@fias/arche-sdk 2.27.0 → 2.29.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.
- package/dist/bridge.d.ts +11 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +20 -0
- package/dist/bridge.js.map +1 -1
- package/dist/bridge.test.js +18 -0
- package/dist/bridge.test.js.map +1 -1
- package/dist/entity-ops.d.ts +17 -1
- package/dist/entity-ops.d.ts.map +1 -1
- package/dist/entity-ops.js +74 -6
- package/dist/entity-ops.js.map +1 -1
- package/dist/generated/permissions.d.ts +1 -1
- package/dist/generated/permissions.d.ts.map +1 -1
- package/dist/generated/permissions.js +7 -1
- package/dist/generated/permissions.js.map +1 -1
- package/dist/hooks.d.ts +193 -4
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +601 -5
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.test.js +492 -0
- package/dist/hooks.test.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +609 -10
- package/dist/limits.d.ts +72 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +75 -0
- package/dist/limits.js.map +1 -0
- package/dist/types.d.ts +540 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/templates/default/AGENTS.md +252 -42
- package/templates/default/CLAUDE.md +252 -42
package/dist/hooks.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FiasContentError = exports.PLUGIN_CONTENT_ENTITY_ID = exports.GRAMMAR_CHECK_ENTITY_ID = exports.NLLB_LANGUAGES = exports.NLLB_TRANSLATION_ENTITY_ID = void 0;
|
|
3
|
+
exports.FiasContentError = exports.FiasLocalStoreError = exports.PLUGIN_LOCAL_STORE_ENTITY_ID = exports.PLUGIN_CONTENT_ENTITY_ID = exports.ArcheAssetError = exports.ARCHE_ASSET_BYTES_ENTITY_ID = exports.GRAMMAR_CHECK_ENTITY_ID = exports.NLLB_LANGUAGES = exports.NLLB_TRANSLATION_ENTITY_ID = void 0;
|
|
4
4
|
exports.useFiasUser = useFiasUser;
|
|
5
5
|
exports.useFiasTheme = useFiasTheme;
|
|
6
6
|
exports.useFiasVendoredAssets = useFiasVendoredAssets;
|
|
7
7
|
exports.useFiasStorage = useFiasStorage;
|
|
8
8
|
exports.useEntityInvocation = useEntityInvocation;
|
|
9
9
|
exports.useTextModels = useTextModels;
|
|
10
|
+
exports.useFiasHolder = useFiasHolder;
|
|
10
11
|
exports.useImageGeneration = useImageGeneration;
|
|
11
12
|
exports.useImageEntities = useImageEntities;
|
|
12
13
|
exports.useClientEntity = useClientEntity;
|
|
@@ -21,10 +22,16 @@ exports.useFiasStore = useFiasStore;
|
|
|
21
22
|
exports.fetchVaultDocumentDownloadUrl = fetchVaultDocumentDownloadUrl;
|
|
22
23
|
exports.useVaultDocuments = useVaultDocuments;
|
|
23
24
|
exports.useArcheAssets = useArcheAssets;
|
|
25
|
+
exports.useArcheAssetBytes = useArcheAssetBytes;
|
|
26
|
+
exports.useLocalStore = useLocalStore;
|
|
24
27
|
exports.useFiasContent = useFiasContent;
|
|
25
28
|
exports.useFiasPreviewState = useFiasPreviewState;
|
|
26
29
|
exports.useDataSubscription = useDataSubscription;
|
|
27
30
|
exports.useVaultUserDocuments = useVaultUserDocuments;
|
|
31
|
+
exports.useFiasConnections = useFiasConnections;
|
|
32
|
+
exports.useFiasX = useFiasX;
|
|
33
|
+
exports.useFiasSchedules = useFiasSchedules;
|
|
34
|
+
exports.useFiasLinkedIn = useFiasLinkedIn;
|
|
28
35
|
exports.useCompanyEmail = useCompanyEmail;
|
|
29
36
|
exports.useCommunityAssets = useCommunityAssets;
|
|
30
37
|
exports.useArcheHandoff = useArcheHandoff;
|
|
@@ -290,6 +297,26 @@ function useTextModels() {
|
|
|
290
297
|
}, [bridge]);
|
|
291
298
|
return models;
|
|
292
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* The Fias workspace the plugin was opened in: the user's own (`personal`) or
|
|
302
|
+
* an organization's (`org`), and the user's role there.
|
|
303
|
+
*
|
|
304
|
+
* Requires `user:profile:read` or `data:workspace`. Returns `null` until the
|
|
305
|
+
* host has delivered it (usually immediately), and when the host could not
|
|
306
|
+
* resolve it. The value never changes while the plugin is open — switching
|
|
307
|
+
* workspace reopens the plugin. It carries no id and no name, and the
|
|
308
|
+
* platform applies the workspace to every call itself.
|
|
309
|
+
*/
|
|
310
|
+
function useFiasHolder() {
|
|
311
|
+
const bridge = useBridge();
|
|
312
|
+
const [holder, setHolder] = (0, react_1.useState)(bridge.getHolder());
|
|
313
|
+
(0, react_1.useEffect)(() => {
|
|
314
|
+
// Init may have arrived between the initial render and this effect.
|
|
315
|
+
setHolder(bridge.getHolder());
|
|
316
|
+
return bridge.onHolderReady(setHolder);
|
|
317
|
+
}, [bridge]);
|
|
318
|
+
return holder;
|
|
319
|
+
}
|
|
293
320
|
/**
|
|
294
321
|
* How long `generate` waits for an image. High-quality renders at large sizes
|
|
295
322
|
* run past two minutes, and the platform keeps working on them for up to
|
|
@@ -947,6 +974,8 @@ function useFiasDataStore() {
|
|
|
947
974
|
* const workspaces = useFiasWorkspaces();
|
|
948
975
|
* const ws = await workspaces.create('Acme Corp');
|
|
949
976
|
* await workspaces.addMember(ws.workspaceId, otherUserId, 'member');
|
|
977
|
+
* // or, inside a Fias org workspace, its members' shared space:
|
|
978
|
+
* const shared = await workspaces.openForWorkspace();
|
|
950
979
|
* // then, with useFiasDataStore():
|
|
951
980
|
* await dataStore.put('ledger', 'tx-1', { amount: 100 }, { workspaceId: ws.workspaceId });
|
|
952
981
|
*
|
|
@@ -958,6 +987,12 @@ function useFiasWorkspaces() {
|
|
|
958
987
|
const res = await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'create', { displayName });
|
|
959
988
|
return res.workspace;
|
|
960
989
|
}, [bridge]);
|
|
990
|
+
const openForWorkspace = (0, react_1.useCallback)(async () => {
|
|
991
|
+
// The workspace is the host's acting one, stamped on the call by the host;
|
|
992
|
+
// the plugin never names it.
|
|
993
|
+
const res = await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'create', { forActingHolder: true });
|
|
994
|
+
return res.workspace;
|
|
995
|
+
}, [bridge]);
|
|
961
996
|
const list = (0, react_1.useCallback)(async () => {
|
|
962
997
|
const res = await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.PLUGIN_WORKSPACES_ENTITY_ID, 'list');
|
|
963
998
|
return res.workspaces;
|
|
@@ -992,7 +1027,27 @@ function useFiasWorkspaces() {
|
|
|
992
1027
|
username,
|
|
993
1028
|
});
|
|
994
1029
|
}, [bridge]);
|
|
995
|
-
return (0, react_1.useMemo)(() => ({
|
|
1030
|
+
return (0, react_1.useMemo)(() => ({
|
|
1031
|
+
create,
|
|
1032
|
+
openForWorkspace,
|
|
1033
|
+
list,
|
|
1034
|
+
get,
|
|
1035
|
+
archive,
|
|
1036
|
+
listMembers,
|
|
1037
|
+
addMember,
|
|
1038
|
+
updateMember,
|
|
1039
|
+
removeMember,
|
|
1040
|
+
}), [
|
|
1041
|
+
create,
|
|
1042
|
+
openForWorkspace,
|
|
1043
|
+
list,
|
|
1044
|
+
get,
|
|
1045
|
+
archive,
|
|
1046
|
+
listMembers,
|
|
1047
|
+
addMember,
|
|
1048
|
+
updateMember,
|
|
1049
|
+
removeMember,
|
|
1050
|
+
]);
|
|
996
1051
|
}
|
|
997
1052
|
/**
|
|
998
1053
|
* Access the in-arche purchase (IAP) store.
|
|
@@ -1457,6 +1512,16 @@ function bytesToBase64(bytes) {
|
|
|
1457
1512
|
* state, and refresh `signedUrl` via `getUrl(assetId)` when it's about
|
|
1458
1513
|
* to expire.
|
|
1459
1514
|
*
|
|
1515
|
+
* The library holds images and short sound effects (MP3 / WAV). `list` and
|
|
1516
|
+
* `index` return images unless you pass `kinds` (e.g. `{ kinds: ['audio'] }`),
|
|
1517
|
+
* and with `kinds` the entries are typed `ArcheLibraryAsset` — narrow on
|
|
1518
|
+
* `contentType` before using an image's size. Keeping sounds here is
|
|
1519
|
+
* optional; music is not accepted. Sound effects that must play on cue need
|
|
1520
|
+
* decoded Web Audio buffers: load library sounds with
|
|
1521
|
+
* {@link useArcheAssetBytes}`().getMany` once per session, or bundle them in
|
|
1522
|
+
* the plugin's JS (base64) — not in `public/`, which only an `<audio>`
|
|
1523
|
+
* element can play.
|
|
1524
|
+
*
|
|
1460
1525
|
* Permission: `assets:read`.
|
|
1461
1526
|
*
|
|
1462
1527
|
* @example
|
|
@@ -1471,12 +1536,14 @@ function useArcheAssets() {
|
|
|
1471
1536
|
cursor: options?.cursor,
|
|
1472
1537
|
limit: options?.limit,
|
|
1473
1538
|
tags: options?.tags,
|
|
1539
|
+
kinds: options?.kinds,
|
|
1474
1540
|
});
|
|
1475
1541
|
}, [bridge]);
|
|
1476
1542
|
const index = (0, react_1.useCallback)(async (options) => {
|
|
1477
1543
|
return bridge.request('arche_assets_index', {
|
|
1478
1544
|
tags: options?.tags,
|
|
1479
1545
|
groupBy: options?.groupBy,
|
|
1546
|
+
kinds: options?.kinds,
|
|
1480
1547
|
});
|
|
1481
1548
|
}, [bridge]);
|
|
1482
1549
|
const getUrl = (0, react_1.useCallback)(async (assetId) => {
|
|
@@ -1484,6 +1551,269 @@ function useArcheAssets() {
|
|
|
1484
1551
|
}, [bridge]);
|
|
1485
1552
|
return (0, react_1.useMemo)(() => ({ list, index, getUrl }), [list, index, getUrl]);
|
|
1486
1553
|
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Read the bytes of assets in this arche's asset library — for Web Audio
|
|
1556
|
+
* (`AudioContext.decodeAudioData`), canvas pixel work, or anything else a
|
|
1557
|
+
* signed URL cannot do (the plugin sandbox has no network). Read each sound
|
|
1558
|
+
* once per session and keep the decoded buffer.
|
|
1559
|
+
*
|
|
1560
|
+
* To load a set of sounds, pass the listed entries to `getMany`: up to 100
|
|
1561
|
+
* are read per call, so a whole sound set takes one or two calls. One call
|
|
1562
|
+
* per sound would trip the bridge's runaway-loop guard (more than 50 calls in
|
|
1563
|
+
* 5 seconds), which blocks every read for 10 seconds — `getBytes` calls made
|
|
1564
|
+
* together are batched for the same reason. The host page fetches each asset,
|
|
1565
|
+
* so each read costs one asset load like an `<img>` does. Unavailable in the
|
|
1566
|
+
* builder preview.
|
|
1567
|
+
*
|
|
1568
|
+
* Sounds are ready to trigger only once their buffers are decoded: wait for
|
|
1569
|
+
* the load before the first cue, and resume the `AudioContext` on the first
|
|
1570
|
+
* user gesture.
|
|
1571
|
+
*
|
|
1572
|
+
* Permissions: `entities:client_invoke` AND `assets:read`.
|
|
1573
|
+
*
|
|
1574
|
+
* @example
|
|
1575
|
+
* const { list } = useArcheAssets();
|
|
1576
|
+
* const { getMany } = useArcheAssetBytes();
|
|
1577
|
+
* const { entries } = await list({ kinds: ['audio'], tags: ['sfx:ui'] });
|
|
1578
|
+
* const ctx = new AudioContext();
|
|
1579
|
+
* const loaded = await getMany(entries);
|
|
1580
|
+
* const buffers = new Map<string, AudioBuffer>();
|
|
1581
|
+
* for (const entry of entries) {
|
|
1582
|
+
* const result = loaded[entry.assetId];
|
|
1583
|
+
* if (result instanceof Error) continue; // result.code says why
|
|
1584
|
+
* buffers.set(entry.name ?? entry.assetId, await ctx.decodeAudioData(result.bytes));
|
|
1585
|
+
* }
|
|
1586
|
+
*/
|
|
1587
|
+
function useArcheAssetBytes() {
|
|
1588
|
+
const bridge = useBridge();
|
|
1589
|
+
const getBytes = (0, react_1.useCallback)(async (asset) => {
|
|
1590
|
+
let ref;
|
|
1591
|
+
if (typeof asset === 'string') {
|
|
1592
|
+
try {
|
|
1593
|
+
ref = await bridge.request('arche_assets_get_url', { assetId: asset });
|
|
1594
|
+
}
|
|
1595
|
+
catch (err) {
|
|
1596
|
+
throw toArcheAssetError(err);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
else {
|
|
1600
|
+
ref = asset;
|
|
1601
|
+
}
|
|
1602
|
+
return enqueueAssetByteRead(bridge, ref);
|
|
1603
|
+
}, [bridge]);
|
|
1604
|
+
const getMany = (0, react_1.useCallback)(async (assets) => {
|
|
1605
|
+
const read = await readAssetBytesBatch(bridge, assets);
|
|
1606
|
+
const out = {};
|
|
1607
|
+
for (const [assetId, result] of read)
|
|
1608
|
+
out[assetId] = result;
|
|
1609
|
+
return out;
|
|
1610
|
+
}, [bridge]);
|
|
1611
|
+
return (0, react_1.useMemo)(() => ({ getBytes, getMany }), [getBytes, getMany]);
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Entity ID of the arche asset bytes client-side entity — the host-side
|
|
1615
|
+
* reader behind {@link useArcheAssetBytes}. Literal here so the SDK
|
|
1616
|
+
* stays bundle-clean (no `@fias/entities` runtime dep).
|
|
1617
|
+
*
|
|
1618
|
+
* SYNC NOTE: must match `ARCHE_ASSET_BYTES_ENTITY_ID` in
|
|
1619
|
+
* `packages/entities/src/integrations/arche-asset-bytes/config.ts`. The
|
|
1620
|
+
* architecture test `sdk-entity-id-constants-match.test.ts` pins this.
|
|
1621
|
+
*/
|
|
1622
|
+
exports.ARCHE_ASSET_BYTES_ENTITY_ID = 'ent_intg_arche_asset_bytes_00000';
|
|
1623
|
+
/** A failed asset byte read (`useArcheAssetBytes`): `code` says why. */
|
|
1624
|
+
class ArcheAssetError extends Error {
|
|
1625
|
+
constructor(message, code) {
|
|
1626
|
+
super(message);
|
|
1627
|
+
this.code = code;
|
|
1628
|
+
this.name = 'ArcheAssetError';
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
exports.ArcheAssetError = ArcheAssetError;
|
|
1632
|
+
/** A listed URL with less than this many seconds left is replaced, not reused. */
|
|
1633
|
+
const ASSET_URL_MIN_REMAINING_SECONDS = 30;
|
|
1634
|
+
/** Most URLs the host reads in one call (`ARCHE_ASSET_BYTES_MAX_URLS` in the executor). */
|
|
1635
|
+
const ASSET_BYTES_MAX_URLS_PER_CALL = 100;
|
|
1636
|
+
/**
|
|
1637
|
+
* Most lapsed URLs one batch replaces with `arche_assets_get_url`, one call
|
|
1638
|
+
* each. Covers URLs that lapse in flight while staying far below the bridge's
|
|
1639
|
+
* 50-calls-in-5-seconds guard; a batch of older entries is answered
|
|
1640
|
+
* `ASSET_URL_EXPIRED` past this, so the plugin lists again (one call).
|
|
1641
|
+
*/
|
|
1642
|
+
const ASSET_URL_MAX_REFRESHES_PER_BATCH = 10;
|
|
1643
|
+
/** One `read` call: the outcome of each URL, in order. Throws when the call itself is refused. */
|
|
1644
|
+
async function readSignedUrls(bridge, signedUrls) {
|
|
1645
|
+
let response;
|
|
1646
|
+
try {
|
|
1647
|
+
response = await bridge.request('client_entity_invoke', { entityId: exports.ARCHE_ASSET_BYTES_ENTITY_ID, input: { verb: 'read', signedUrls } }, CLIENT_ENTITY_INVOKE_TIMEOUT_MS);
|
|
1648
|
+
}
|
|
1649
|
+
catch (err) {
|
|
1650
|
+
throw toArcheAssetError(err);
|
|
1651
|
+
}
|
|
1652
|
+
if ('error' in response) {
|
|
1653
|
+
throw new ArcheAssetError(response.error.message, response.error.code);
|
|
1654
|
+
}
|
|
1655
|
+
return response.results;
|
|
1656
|
+
}
|
|
1657
|
+
function fileError(result) {
|
|
1658
|
+
return result && 'error' in result
|
|
1659
|
+
? new ArcheAssetError(result.error.message, result.error.code)
|
|
1660
|
+
: new ArcheAssetError('the asset could not be read', 'ASSET_UNAVAILABLE');
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Read `items` in calls of up to 100 URLs, recording each outcome in `out`.
|
|
1664
|
+
* Assets that did not fit a call's byte budget beside the others go again
|
|
1665
|
+
* together until a pass reads nothing more (then each is too large on its
|
|
1666
|
+
* own). Returns the reused URLs that lapsed in flight, for a fresh URL.
|
|
1667
|
+
*/
|
|
1668
|
+
async function readInBatches(bridge, items, out) {
|
|
1669
|
+
const lapsed = [];
|
|
1670
|
+
let queue = items;
|
|
1671
|
+
while (queue.length > 0) {
|
|
1672
|
+
const overBudget = [];
|
|
1673
|
+
let readCount = 0;
|
|
1674
|
+
for (let at = 0; at < queue.length; at += ASSET_BYTES_MAX_URLS_PER_CALL) {
|
|
1675
|
+
const chunk = queue.slice(at, at + ASSET_BYTES_MAX_URLS_PER_CALL);
|
|
1676
|
+
const results = await readSignedUrls(bridge, chunk.map((item) => item.signedUrl));
|
|
1677
|
+
chunk.forEach((item, i) => {
|
|
1678
|
+
const result = results[i];
|
|
1679
|
+
if (result && !('error' in result)) {
|
|
1680
|
+
out.set(item.assetId, result);
|
|
1681
|
+
readCount += 1;
|
|
1682
|
+
}
|
|
1683
|
+
else if (result?.error.code === 'ASSET_URL_EXPIRED' && !item.refreshed) {
|
|
1684
|
+
// A reused URL can lapse before the host's fetch lands (or the
|
|
1685
|
+
// device clock runs fast); a fresh one recovers it.
|
|
1686
|
+
lapsed.push(item);
|
|
1687
|
+
}
|
|
1688
|
+
else if (result?.error.code === 'ASSET_TOO_LARGE' && chunk.length > 1) {
|
|
1689
|
+
overBudget.push(item);
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
out.set(item.assetId, fileError(result));
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
if (readCount === 0) {
|
|
1697
|
+
for (const item of overBudget) {
|
|
1698
|
+
out.set(item.assetId, new ArcheAssetError('the asset is too large to read', 'ASSET_TOO_LARGE'));
|
|
1699
|
+
}
|
|
1700
|
+
break;
|
|
1701
|
+
}
|
|
1702
|
+
queue = overBudget;
|
|
1703
|
+
}
|
|
1704
|
+
return lapsed;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Read `refs` in as few host calls as possible: reuse each listed URL while it
|
|
1708
|
+
* has time left, replace a bounded number of lapsed ones, and send up to 100
|
|
1709
|
+
* URLs per call. Throws only when a call is refused as a whole.
|
|
1710
|
+
*/
|
|
1711
|
+
async function readAssetBytesBatch(bridge, refs) {
|
|
1712
|
+
const out = new Map();
|
|
1713
|
+
let refreshesLeft = ASSET_URL_MAX_REFRESHES_PER_BATCH;
|
|
1714
|
+
// A fresh URL for each lapsed item, within the batch's refresh allowance.
|
|
1715
|
+
const refresh = async (assetIds) => {
|
|
1716
|
+
const fresh = [];
|
|
1717
|
+
for (const assetId of assetIds) {
|
|
1718
|
+
if (refreshesLeft <= 0) {
|
|
1719
|
+
out.set(assetId, new ArcheAssetError('the asset URL has expired; list the assets again for fresh URLs', 'ASSET_URL_EXPIRED'));
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
refreshesLeft -= 1;
|
|
1723
|
+
try {
|
|
1724
|
+
const asset = await bridge.request('arche_assets_get_url', { assetId });
|
|
1725
|
+
fresh.push({ assetId, signedUrl: asset.signedUrl, refreshed: true });
|
|
1726
|
+
}
|
|
1727
|
+
catch (err) {
|
|
1728
|
+
out.set(assetId, toArcheAssetError(err));
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return fresh;
|
|
1732
|
+
};
|
|
1733
|
+
// One read per asset, whatever the caller passed twice.
|
|
1734
|
+
const unique = new Map();
|
|
1735
|
+
for (const ref of refs)
|
|
1736
|
+
if (!unique.has(ref.assetId))
|
|
1737
|
+
unique.set(ref.assetId, ref);
|
|
1738
|
+
const nowSeconds = Date.now() / 1000;
|
|
1739
|
+
const reusable = [];
|
|
1740
|
+
const stale = [];
|
|
1741
|
+
for (const ref of unique.values()) {
|
|
1742
|
+
if (ref.expiresAt - nowSeconds > ASSET_URL_MIN_REMAINING_SECONDS) {
|
|
1743
|
+
reusable.push({ assetId: ref.assetId, signedUrl: ref.signedUrl, refreshed: false });
|
|
1744
|
+
}
|
|
1745
|
+
else {
|
|
1746
|
+
stale.push(ref.assetId);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
const pending = [...reusable, ...(await refresh(stale))];
|
|
1750
|
+
const lapsed = await readInBatches(bridge, pending, out);
|
|
1751
|
+
if (lapsed.length > 0) {
|
|
1752
|
+
await readInBatches(bridge, await refresh(lapsed.map((item) => item.assetId)), out);
|
|
1753
|
+
}
|
|
1754
|
+
return out;
|
|
1755
|
+
}
|
|
1756
|
+
const pendingAssetByteReads = new WeakMap();
|
|
1757
|
+
function enqueueAssetByteRead(bridge, ref) {
|
|
1758
|
+
let batch = pendingAssetByteReads.get(bridge);
|
|
1759
|
+
if (!batch) {
|
|
1760
|
+
const created = { refs: [], waiters: [] };
|
|
1761
|
+
pendingAssetByteReads.set(bridge, created);
|
|
1762
|
+
batch = created;
|
|
1763
|
+
void Promise.resolve().then(() => flushAssetByteReads(bridge, created));
|
|
1764
|
+
}
|
|
1765
|
+
batch.refs.push(ref);
|
|
1766
|
+
const queued = batch;
|
|
1767
|
+
return new Promise((resolve, reject) => {
|
|
1768
|
+
queued.waiters.push({ assetId: ref.assetId, resolve, reject });
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
async function flushAssetByteReads(bridge, batch) {
|
|
1772
|
+
pendingAssetByteReads.delete(bridge);
|
|
1773
|
+
let results;
|
|
1774
|
+
try {
|
|
1775
|
+
results = await readAssetBytesBatch(bridge, batch.refs);
|
|
1776
|
+
}
|
|
1777
|
+
catch (err) {
|
|
1778
|
+
const failure = toArcheAssetError(err);
|
|
1779
|
+
for (const waiter of batch.waiters)
|
|
1780
|
+
waiter.reject(failure);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const handedOut = new Set();
|
|
1784
|
+
for (const waiter of batch.waiters) {
|
|
1785
|
+
const result = results.get(waiter.assetId) ??
|
|
1786
|
+
new ArcheAssetError('the asset could not be read', 'ASSET_UNAVAILABLE');
|
|
1787
|
+
if (result instanceof ArcheAssetError) {
|
|
1788
|
+
waiter.reject(result);
|
|
1789
|
+
}
|
|
1790
|
+
else if (handedOut.has(waiter.assetId)) {
|
|
1791
|
+
// Two callers asked for one asset: each gets its own buffer, because
|
|
1792
|
+
// `decodeAudioData` detaches the one it is given.
|
|
1793
|
+
waiter.resolve({ ...result, bytes: result.bytes.slice(0) });
|
|
1794
|
+
}
|
|
1795
|
+
else {
|
|
1796
|
+
handedOut.add(waiter.assetId);
|
|
1797
|
+
waiter.resolve(result);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
function toArcheAssetError(err) {
|
|
1802
|
+
if (err instanceof ArcheAssetError)
|
|
1803
|
+
return err;
|
|
1804
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1805
|
+
const code = err?.code;
|
|
1806
|
+
if (/permission denied/i.test(message)) {
|
|
1807
|
+
return new ArcheAssetError(`${message} — useArcheAssetBytes needs both "assets:read" and "entities:client_invoke" in fias-plugin.json`, 'PERMISSION_DENIED');
|
|
1808
|
+
}
|
|
1809
|
+
if (code === 'RATE_LIMITED' || code === 'RATE_LIMIT' || /rate limit/i.test(message)) {
|
|
1810
|
+
return new ArcheAssetError(message, 'RATE_LIMIT');
|
|
1811
|
+
}
|
|
1812
|
+
if (/rapid-fire loop|runaway loop/i.test(message)) {
|
|
1813
|
+
return new ArcheAssetError(`${message} Load many assets with useArcheAssetBytes().getMany(entries) — one call per 100.`, 'RATE_LIMIT');
|
|
1814
|
+
}
|
|
1815
|
+
return new ArcheAssetError(message, 'ASSET_UNAVAILABLE');
|
|
1816
|
+
}
|
|
1487
1817
|
/**
|
|
1488
1818
|
* Entity ID of the plugin content pack client-side entity (ADR-021) — the
|
|
1489
1819
|
* host-side reader behind {@link useFiasContent}. Literal here so the SDK
|
|
@@ -1494,6 +1824,143 @@ function useArcheAssets() {
|
|
|
1494
1824
|
* architecture test `sdk-entity-id-constants-match.test.ts` pins this.
|
|
1495
1825
|
*/
|
|
1496
1826
|
exports.PLUGIN_CONTENT_ENTITY_ID = 'ent_intg_plugin_content_00000000';
|
|
1827
|
+
/**
|
|
1828
|
+
* Entity ID of the plugin local store client-side entity (ADR-031) — the
|
|
1829
|
+
* host-side store behind {@link useLocalStore}. Literal here so the SDK stays
|
|
1830
|
+
* bundle-clean (no `@fias/entities` runtime dep).
|
|
1831
|
+
*
|
|
1832
|
+
* SYNC NOTE: must match `PLUGIN_LOCAL_STORE_ENTITY_ID` in
|
|
1833
|
+
* `packages/entities/src/integrations/plugin-local-store/config.ts`. The
|
|
1834
|
+
* architecture test `sdk-entity-id-constants-match.test.ts` pins this.
|
|
1835
|
+
*/
|
|
1836
|
+
exports.PLUGIN_LOCAL_STORE_ENTITY_ID = 'ent_intg_plugin_local_store_0000';
|
|
1837
|
+
/** A failed local-store call: `code` says why. */
|
|
1838
|
+
class FiasLocalStoreError extends Error {
|
|
1839
|
+
constructor(message, code) {
|
|
1840
|
+
super(message);
|
|
1841
|
+
this.code = code;
|
|
1842
|
+
this.name = 'FiasLocalStoreError';
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
exports.FiasLocalStoreError = FiasLocalStoreError;
|
|
1846
|
+
function toLocalStoreError(err) {
|
|
1847
|
+
if (err instanceof FiasLocalStoreError)
|
|
1848
|
+
return err;
|
|
1849
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1850
|
+
// The bridge's centralized gates carry a code; the registry's own refusals
|
|
1851
|
+
// (per-entity permission, per-entity rate bucket) are plain Errors, so the
|
|
1852
|
+
// message is the fallback, never the first look.
|
|
1853
|
+
const raw = err?.code;
|
|
1854
|
+
const code = raw === 'PERMISSION_DENIED' || /permission denied/i.test(message)
|
|
1855
|
+
? 'PERMISSION_DENIED'
|
|
1856
|
+
: raw === 'RATE_LIMIT' || raw === 'RATE_LIMITED' || /rate limit/i.test(message)
|
|
1857
|
+
? 'RATE_LIMIT'
|
|
1858
|
+
: 'LOCAL_STORE_UNAVAILABLE';
|
|
1859
|
+
return new FiasLocalStoreError(message, code);
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Keep working data on the user's device — a recovery journal, drafts, a
|
|
1863
|
+
* cache — so a refresh, a crash or a closed tab loses nothing. Writes take
|
|
1864
|
+
* milliseconds and never touch the network: the host page keeps the data in
|
|
1865
|
+
* its own browser storage, in a space only this plugin can read, for the
|
|
1866
|
+
* signed-in user in the workspace they are working in (Personal and each org
|
|
1867
|
+
* workspace keep separate data; switching workspace switches the store).
|
|
1868
|
+
* Signing out clears it.
|
|
1869
|
+
*
|
|
1870
|
+
* This is NOT where a user's work is kept. It is best-effort device storage —
|
|
1871
|
+
* a browser may clear site data, and nothing here reaches another device —
|
|
1872
|
+
* so write the journal here and save the user's files through the Vault
|
|
1873
|
+
* (`useVaultUserDocuments().saveContent`, `upload`, or the Save dialog).
|
|
1874
|
+
*
|
|
1875
|
+
* Values: a string, bytes (`ArrayBuffer` / `Uint8Array`), or any JSON value;
|
|
1876
|
+
* `get` returns the same kind. One value ≤ 64 MB; the whole store for this
|
|
1877
|
+
* user ≤ 256 MB. Over budget, `put` throws `LOCAL_STORE_QUOTA_EXCEEDED`
|
|
1878
|
+
* rather than evicting — decide what to keep with `estimate()` and `list()`.
|
|
1879
|
+
* For a large snapshot (an image, a big document), put BYTES: a JSON value is
|
|
1880
|
+
* validated and serialized on the host page, which costs time proportional
|
|
1881
|
+
* to its size on every write, while bytes are only copied. Restore a journal
|
|
1882
|
+
* with `getMany(keys)` — one call for up to 100 entries, instead of one call
|
|
1883
|
+
* per key.
|
|
1884
|
+
*
|
|
1885
|
+
* Permission: `storage:local`.
|
|
1886
|
+
*
|
|
1887
|
+
* @example
|
|
1888
|
+
* const store = useLocalStore();
|
|
1889
|
+
* await store.put(`session/${docId}`, snapshotJson); // on every change
|
|
1890
|
+
* const saved = await store.getJson<Snapshot>(`session/${docId}`); // on load
|
|
1891
|
+
* await store.delete(`session/${docId}`); // on close
|
|
1892
|
+
*/
|
|
1893
|
+
function useLocalStore() {
|
|
1894
|
+
const bridge = useBridge();
|
|
1895
|
+
const call = (0, react_1.useCallback)(async (input) => {
|
|
1896
|
+
let response;
|
|
1897
|
+
try {
|
|
1898
|
+
response = await bridge.request('client_entity_invoke', { entityId: exports.PLUGIN_LOCAL_STORE_ENTITY_ID, input }, CLIENT_ENTITY_INVOKE_TIMEOUT_MS);
|
|
1899
|
+
}
|
|
1900
|
+
catch (err) {
|
|
1901
|
+
throw toLocalStoreError(err);
|
|
1902
|
+
}
|
|
1903
|
+
if (response?.error) {
|
|
1904
|
+
throw new FiasLocalStoreError(response.error.message, response.error.code);
|
|
1905
|
+
}
|
|
1906
|
+
return response ?? {};
|
|
1907
|
+
}, [bridge]);
|
|
1908
|
+
const get = (0, react_1.useCallback)(async (key) => (await call({ verb: 'get', key })).entry ?? null, [call]);
|
|
1909
|
+
const getText = (0, react_1.useCallback)(async (key) => {
|
|
1910
|
+
const entry = await get(key);
|
|
1911
|
+
if (!entry)
|
|
1912
|
+
return null;
|
|
1913
|
+
if (typeof entry.value !== 'string') {
|
|
1914
|
+
throw new FiasLocalStoreError(`"${key}" does not hold text`, 'LOCAL_STORE_WRONG_KIND');
|
|
1915
|
+
}
|
|
1916
|
+
return entry.value;
|
|
1917
|
+
}, [get]);
|
|
1918
|
+
const getBytes = (0, react_1.useCallback)(async (key) => {
|
|
1919
|
+
const entry = await get(key);
|
|
1920
|
+
if (!entry)
|
|
1921
|
+
return null;
|
|
1922
|
+
if (!(entry.value instanceof ArrayBuffer)) {
|
|
1923
|
+
throw new FiasLocalStoreError(`"${key}" does not hold bytes`, 'LOCAL_STORE_WRONG_KIND');
|
|
1924
|
+
}
|
|
1925
|
+
return entry.value;
|
|
1926
|
+
}, [get]);
|
|
1927
|
+
const getJson = (0, react_1.useCallback)(async (key) => {
|
|
1928
|
+
const entry = await get(key);
|
|
1929
|
+
if (!entry)
|
|
1930
|
+
return null;
|
|
1931
|
+
if (entry.value instanceof ArrayBuffer) {
|
|
1932
|
+
throw new FiasLocalStoreError(`"${key}" holds bytes, not JSON`, 'LOCAL_STORE_WRONG_KIND');
|
|
1933
|
+
}
|
|
1934
|
+
// A top-level string is always stored as text, so `put(key,
|
|
1935
|
+
// JSON.stringify(x))` reads back here the same as `put(key, x)`.
|
|
1936
|
+
if (typeof entry.value === 'string') {
|
|
1937
|
+
try {
|
|
1938
|
+
return JSON.parse(entry.value);
|
|
1939
|
+
}
|
|
1940
|
+
catch {
|
|
1941
|
+
throw new FiasLocalStoreError(`"${key}" holds text that is not JSON`, 'LOCAL_STORE_WRONG_KIND');
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
return entry.value;
|
|
1945
|
+
}, [get]);
|
|
1946
|
+
const getMany = (0, react_1.useCallback)(async (keys) => (await call({ verb: 'get_many', keys }))
|
|
1947
|
+
.entries ?? [], [call]);
|
|
1948
|
+
const put = (0, react_1.useCallback)(async (key, value) => ({
|
|
1949
|
+
sizeBytes: (await call({ verb: 'put', key, value })).sizeBytes ?? 0,
|
|
1950
|
+
}), [call]);
|
|
1951
|
+
const del = (0, react_1.useCallback)(async (key) => {
|
|
1952
|
+
await call({ verb: 'delete', key });
|
|
1953
|
+
}, [call]);
|
|
1954
|
+
const list = (0, react_1.useCallback)(async (prefix) => (await call({ verb: 'list', ...(prefix !== undefined ? { prefix } : {}) })).entries ?? [], [call]);
|
|
1955
|
+
const clear = (0, react_1.useCallback)(async () => ({
|
|
1956
|
+
deleted: (await call({ verb: 'clear' })).deleted ?? 0,
|
|
1957
|
+
}), [call]);
|
|
1958
|
+
const estimate = (0, react_1.useCallback)(async () => {
|
|
1959
|
+
const res = await call({ verb: 'estimate' });
|
|
1960
|
+
return { usedBytes: res.usedBytes ?? 0, budgetBytes: res.budgetBytes ?? 0 };
|
|
1961
|
+
}, [call]);
|
|
1962
|
+
return (0, react_1.useMemo)(() => ({ get, getMany, getText, getBytes, getJson, put, delete: del, list, clear, estimate }), [get, getMany, getText, getBytes, getJson, put, del, list, clear, estimate]);
|
|
1963
|
+
}
|
|
1497
1964
|
/** A failed content read: `code` says why, `path` names the file when there is one. */
|
|
1498
1965
|
class FiasContentError extends Error {
|
|
1499
1966
|
constructor(message, code, path) {
|
|
@@ -1975,6 +2442,133 @@ async function fetchCommunityAssetUrls(bridge, assetIds) {
|
|
|
1975
2442
|
}
|
|
1976
2443
|
return hits;
|
|
1977
2444
|
}
|
|
2445
|
+
/** Connecting waits on a person (and maybe a provider sign-in in a popup). */
|
|
2446
|
+
const CONNECT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
2447
|
+
/**
|
|
2448
|
+
* Outside accounts (X, LinkedIn, …) the workspace the app is open in lets it
|
|
2449
|
+
* use (ADR-029 §2). Requires `connections:use`.
|
|
2450
|
+
*
|
|
2451
|
+
* The app never sees a credential. `connect(provider)` asks the host to show
|
|
2452
|
+
* its connect screen, where the person connects an account or lets this app
|
|
2453
|
+
* use one already connected; the host makes every grant. Acting as an account
|
|
2454
|
+
* (posting, reading its audience) is a verb on the provider's own entity.
|
|
2455
|
+
*
|
|
2456
|
+
* @example
|
|
2457
|
+
* const connections = useFiasConnections();
|
|
2458
|
+
* const accounts = await connections.list();
|
|
2459
|
+
* const x = accounts.find((a) => a.provider === 'x') ?? (await connections.connect('x'));
|
|
2460
|
+
*/
|
|
2461
|
+
function useFiasConnections() {
|
|
2462
|
+
const bridge = useBridge();
|
|
2463
|
+
const list = (0, react_1.useCallback)(async () => {
|
|
2464
|
+
const res = await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.CONNECTIONS_ENTITY_ID, 'list');
|
|
2465
|
+
return res.connections;
|
|
2466
|
+
}, [bridge]);
|
|
2467
|
+
const connect = (0, react_1.useCallback)(async (provider) => {
|
|
2468
|
+
const result = await bridge.request('client_entity_invoke', { entityId: entity_ops_1.CONNECTIONS_CONNECT_ENTITY_ID, input: { provider } }, CONNECT_TIMEOUT_MS);
|
|
2469
|
+
if (result && 'error' in result) {
|
|
2470
|
+
throw new bridge_1.FiasBridgeError(result.error.message, result.error.code);
|
|
2471
|
+
}
|
|
2472
|
+
return result;
|
|
2473
|
+
}, [bridge]);
|
|
2474
|
+
const release = (0, react_1.useCallback)(async (connectionId) => {
|
|
2475
|
+
await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.CONNECTIONS_ENTITY_ID, 'release', { connectionId });
|
|
2476
|
+
}, [bridge]);
|
|
2477
|
+
return (0, react_1.useMemo)(() => ({ list, connect, release }), [list, connect, release]);
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* X, as an account the workspace this app is open in let it use (ADR-029 §3).
|
|
2481
|
+
*
|
|
2482
|
+
* Requires `connections:use` in `fias-plugin.json`. Get a `connectionId` from
|
|
2483
|
+
* `useFiasConnections()` (`list`, or `connect('x')`). Each call is charged to
|
|
2484
|
+
* the user as X prices it: a read by what it returns, a post per post — and a
|
|
2485
|
+
* post with a link (any URL or web address) costs more than a plain one. `publishPost` puts a confirm sheet in front of the
|
|
2486
|
+
* person — the platform's own words: the account, the workspace, the post —
|
|
2487
|
+
* and resolves only after they approve. A decline rejects with
|
|
2488
|
+
* `FiasBridgeError` code `USER_DECLINED`. Never available in the dev harness:
|
|
2489
|
+
* a post needs a person in the Fias app.
|
|
2490
|
+
*
|
|
2491
|
+
* @example
|
|
2492
|
+
* ```tsx
|
|
2493
|
+
* const x = useFiasX();
|
|
2494
|
+
* const { posts } = await x.publishPost({ connectionId, posts: [{ text: 'Hello' }] });
|
|
2495
|
+
* ```
|
|
2496
|
+
*/
|
|
2497
|
+
function useFiasX() {
|
|
2498
|
+
const bridge = useBridge();
|
|
2499
|
+
return (0, react_1.useMemo)(() => ({
|
|
2500
|
+
publishPost: (params) => (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.X_ENTITY_ID, 'publish_post', { ...params }),
|
|
2501
|
+
searchUsers: async (connectionId, query, options) => (await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.X_ENTITY_ID, 'search_users', {
|
|
2502
|
+
connectionId,
|
|
2503
|
+
query,
|
|
2504
|
+
...options,
|
|
2505
|
+
})).users,
|
|
2506
|
+
getUserPosts: async (connectionId, options) => (await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.X_ENTITY_ID, 'get_user_posts', {
|
|
2507
|
+
connectionId,
|
|
2508
|
+
...options,
|
|
2509
|
+
})).posts,
|
|
2510
|
+
getPostMetrics: async (connectionId, postIds) => (await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.X_ENTITY_ID, 'get_post_metrics', { connectionId, postIds })).metrics,
|
|
2511
|
+
getAccountStats: (connectionId) => (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.X_ENTITY_ID, 'get_account_stats', {
|
|
2512
|
+
connectionId,
|
|
2513
|
+
}),
|
|
2514
|
+
}), [bridge]);
|
|
2515
|
+
}
|
|
2516
|
+
/**
|
|
2517
|
+
* Schedule one call for later (ADR-029 §5) — e.g. a post at 9 AM tomorrow.
|
|
2518
|
+
*
|
|
2519
|
+
* Requires `schedule:invoke` in `fias-plugin.json`, plus whatever the call
|
|
2520
|
+
* itself needs (`connections:use` to post). For an action outside Fias the
|
|
2521
|
+
* person approves the exact call AND its time in a confirm sheet first; a
|
|
2522
|
+
* decline rejects with `USER_DECLINED`. When it is due every check runs again
|
|
2523
|
+
* — if the account was disconnected or the app lost access, nothing is sent
|
|
2524
|
+
* and `list()` shows it as `refused`. There is no edit: cancel and schedule
|
|
2525
|
+
* again (a new approval).
|
|
2526
|
+
*
|
|
2527
|
+
* @example
|
|
2528
|
+
* ```tsx
|
|
2529
|
+
* const schedules = useFiasSchedules();
|
|
2530
|
+
* await schedules.schedule({
|
|
2531
|
+
* entityId: X_ENTITY_ID,
|
|
2532
|
+
* verb: 'publish_post',
|
|
2533
|
+
* payload: { connectionId, posts: [{ text: 'Launch day' }] },
|
|
2534
|
+
* fireAt: tomorrowAtNine,
|
|
2535
|
+
* });
|
|
2536
|
+
* ```
|
|
2537
|
+
*/
|
|
2538
|
+
function useFiasSchedules() {
|
|
2539
|
+
const bridge = useBridge();
|
|
2540
|
+
return (0, react_1.useMemo)(() => ({
|
|
2541
|
+
schedule: (params) => (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.SCHEDULED_INVOCATIONS_ENTITY_ID, 'schedule', {
|
|
2542
|
+
entityId: params.entityId,
|
|
2543
|
+
verb: params.verb,
|
|
2544
|
+
payload: params.payload,
|
|
2545
|
+
fireAt: params.fireAt instanceof Date ? params.fireAt.toISOString() : params.fireAt,
|
|
2546
|
+
}),
|
|
2547
|
+
list: async (options) => (await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.SCHEDULED_INVOCATIONS_ENTITY_ID, 'list', {
|
|
2548
|
+
...(options?.limit !== undefined ? { limit: options.limit } : {}),
|
|
2549
|
+
...(options?.scheduleIds ? { scheduleIds: options.scheduleIds } : {}),
|
|
2550
|
+
})).schedules,
|
|
2551
|
+
cancel: async (scheduleId) => {
|
|
2552
|
+
await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.SCHEDULED_INVOCATIONS_ENTITY_ID, 'cancel', { scheduleId });
|
|
2553
|
+
},
|
|
2554
|
+
}), [bridge]);
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* LinkedIn, as a member account the workspace this app is open in let it use
|
|
2558
|
+
* (ADR-029 §3). Requires `connections:use`; get a `connectionId` from
|
|
2559
|
+
* `useFiasConnections()`. Each call is charged to the user. No post-metrics or
|
|
2560
|
+
* audience calls yet: LinkedIn serves them only with analytics scopes the
|
|
2561
|
+
* connection does not ask for. `publishPost` puts
|
|
2562
|
+
* a confirm sheet in front of the person and resolves only after they approve;
|
|
2563
|
+
* a decline rejects with code `USER_DECLINED`.
|
|
2564
|
+
*/
|
|
2565
|
+
function useFiasLinkedIn() {
|
|
2566
|
+
const bridge = useBridge();
|
|
2567
|
+
return (0, react_1.useMemo)(() => ({
|
|
2568
|
+
publishPost: (params) => (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.LINKEDIN_ENTITY_ID, 'publish_post', { ...params }),
|
|
2569
|
+
searchOrganizations: async (connectionId, vanityName) => (await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.LINKEDIN_ENTITY_ID, 'search_organizations', { connectionId, query: vanityName })).organizations,
|
|
2570
|
+
}), [bridge]);
|
|
2571
|
+
}
|
|
1978
2572
|
/**
|
|
1979
2573
|
* Send email AS the Company this app is being used in (ADR-023), from the
|
|
1980
2574
|
* Company's own verified domain — not from Fias.
|
|
@@ -2130,9 +2724,11 @@ function useCommunityAssets() {
|
|
|
2130
2724
|
* mutate the source. And you are the authoritative type gate — if you cannot
|
|
2131
2725
|
* open this file, tell the user plainly rather than opening something broken.
|
|
2132
2726
|
*
|
|
2133
|
-
* To be offered as a destination at all,
|
|
2134
|
-
*
|
|
2135
|
-
*
|
|
2727
|
+
* To be offered as a destination at all, declare what you open in
|
|
2728
|
+
* `fias-plugin.json` (`"fileIntake": { "kinds": ["document"], "types": [".docx"] }`;
|
|
2729
|
+
* `types` narrows a kind to the files you really open) and have Fias approve
|
|
2730
|
+
* it; declaring this hook alone does not enrol you. A kind or type added later
|
|
2731
|
+
* stays off until it is approved too.
|
|
2136
2732
|
*/
|
|
2137
2733
|
function useArcheHandoff(onHandoff) {
|
|
2138
2734
|
const bridge = useBridge();
|