@thatopen/services 0.7.0 → 0.9.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/README.md +62 -62
- package/dist/built-in/index.d.ts +66 -41
- package/dist/cli/lib/revit-addin.d.ts +8 -0
- package/dist/cli.js +154 -13
- package/dist/core/client.d.ts +6 -0
- package/dist/core/platform-client.d.ts +139 -0
- package/dist/core/platform-client.live.test.d.ts +1 -0
- package/dist/core/platform-client.notifications.test.d.ts +1 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +635 -445
- package/dist/types/notifications.d.ts +54 -0
- package/dist/vite-env.d.ts +1 -1
- package/docs/ai-quickstart.md +120 -1
- package/docs/cli/create.md +29 -29
- package/docs/cli/local-server.md +38 -38
- package/docs/cli/login.md +32 -32
- package/docs/cli/publish.md +31 -31
- package/docs/cli/run.md +26 -26
- package/docs/cli/serve.md +43 -43
- package/docs/cli/swap.md +30 -30
- package/docs/flow-plugin-guide.md +26 -0
- package/docs/revit-collab-quickstart.md +26 -17
- package/docs/rhino-interop-quickstart.md +74 -7
- package/package.json +1 -1
- package/resources/AGENTS.md +40 -40
- package/src/built-in/CDEManager/example.ts +214 -214
- package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
- package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
- package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
- package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
- package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
- package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
- package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
- package/src/built-in/UIManager/src/app/example.ts +97 -97
- package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
- package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
- package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
- package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
- package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
- package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
- package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
- package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
- package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
- package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
- package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
- package/src/built-in/UIManager/src/models-list/example.ts +122 -122
- package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
- package/src/built-in/index.ts +66 -41
- package/src/cli/templates/app/index.html +35 -35
- package/src/cli/templates/app/package.json +32 -32
- package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
- package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
- package/src/cli/templates/app/src/bim-components/index.ts +1 -1
- package/src/cli/templates/app/src/globals.ts +1 -1
- package/src/cli/templates/app/src/main.history.ts +191 -0
- package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
- package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
- package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
- package/src/cli/templates/app/src/ui-components/index.ts +2 -2
- package/src/cli/templates/app/tsconfig.json +16 -16
- package/src/cli/templates/app/vite.config.js +57 -57
- package/src/cli/templates/cloud-component/README.md +205 -205
- package/src/cli/templates/cloud-component/_thatopen +5 -5
- package/src/cli/templates/cloud-component/declarations.json +4 -4
- package/src/cli/templates/cloud-component/package.json +21 -21
- package/src/cli/templates/cloud-component/src/main.ts +70 -70
- package/src/cli/templates/cloud-component/tsconfig.json +16 -16
- package/src/cli/templates/cloud-component/vite.config.js +46 -46
- package/src/cli/templates/shared/AGENTS.md +8 -8
- package/src/cli/templates/shared/CLAUDE.md +1 -1
- package/src/cli/templates/shared/_gitignore +5 -5
package/dist/cli.js
CHANGED
|
@@ -4205,8 +4205,8 @@ class BaseXHR extends Polling {
|
|
|
4205
4205
|
/**
|
|
4206
4206
|
* Sends data.
|
|
4207
4207
|
*
|
|
4208
|
-
* @param {String} data to send.
|
|
4209
|
-
* @param {Function} called upon flush.
|
|
4208
|
+
* @param {String} data - data to send.
|
|
4209
|
+
* @param {Function} fn - called upon flush.
|
|
4210
4210
|
* @private
|
|
4211
4211
|
*/
|
|
4212
4212
|
doWrite(data, fn) {
|
|
@@ -4959,7 +4959,7 @@ class SocketWithoutUpgrade extends Emitter {
|
|
|
4959
4959
|
/**
|
|
4960
4960
|
* Sends a packet.
|
|
4961
4961
|
*
|
|
4962
|
-
* @param {String} type
|
|
4962
|
+
* @param {String} type - packet type.
|
|
4963
4963
|
* @param {String} data.
|
|
4964
4964
|
* @param {Object} options.
|
|
4965
4965
|
* @param {Function} fn - callback function.
|
|
@@ -5194,11 +5194,12 @@ class SocketWithUpgrade extends SocketWithoutUpgrade {
|
|
|
5194
5194
|
}
|
|
5195
5195
|
let Socket$1 = class Socket extends SocketWithUpgrade {
|
|
5196
5196
|
constructor(uri, opts = {}) {
|
|
5197
|
-
const
|
|
5197
|
+
const isOptionsOnly = typeof uri === "object";
|
|
5198
|
+
const o = isOptionsOnly ? { ...uri } : { ...opts };
|
|
5198
5199
|
if (!o.transports || o.transports && typeof o.transports[0] === "string") {
|
|
5199
5200
|
o.transports = (o.transports || ["polling", "websocket", "webtransport"]).map((transportName) => transports[transportName]).filter((t) => !!t);
|
|
5200
5201
|
}
|
|
5201
|
-
super(uri, o);
|
|
5202
|
+
super(isOptionsOnly ? o : uri, o);
|
|
5202
5203
|
}
|
|
5203
5204
|
};
|
|
5204
5205
|
function url(uri, path2 = "", loc) {
|
|
@@ -5419,12 +5420,13 @@ class Encoder {
|
|
|
5419
5420
|
class Decoder extends Emitter {
|
|
5420
5421
|
/**
|
|
5421
5422
|
* Decoder constructor
|
|
5422
|
-
*
|
|
5423
|
-
* @param {function} reviver - custom reviver to pass down to JSON.stringify
|
|
5424
5423
|
*/
|
|
5425
|
-
constructor(
|
|
5424
|
+
constructor(opts) {
|
|
5426
5425
|
super();
|
|
5427
|
-
this.
|
|
5426
|
+
this.opts = Object.assign({
|
|
5427
|
+
reviver: void 0,
|
|
5428
|
+
maxAttachments: 10
|
|
5429
|
+
}, typeof opts === "function" ? { reviver: opts } : opts);
|
|
5428
5430
|
}
|
|
5429
5431
|
/**
|
|
5430
5432
|
* Decodes an encoded packet string into packet JSON.
|
|
@@ -5484,7 +5486,13 @@ class Decoder extends Emitter {
|
|
|
5484
5486
|
if (buf != Number(buf) || str.charAt(i) !== "-") {
|
|
5485
5487
|
throw new Error("Illegal attachments");
|
|
5486
5488
|
}
|
|
5487
|
-
|
|
5489
|
+
const n = Number(buf);
|
|
5490
|
+
if (!isInteger(n) || n < 0) {
|
|
5491
|
+
throw new Error("Illegal attachments");
|
|
5492
|
+
} else if (n > this.opts.maxAttachments) {
|
|
5493
|
+
throw new Error("too many attachments");
|
|
5494
|
+
}
|
|
5495
|
+
p.attachments = n;
|
|
5488
5496
|
}
|
|
5489
5497
|
if ("/" === str.charAt(i + 1)) {
|
|
5490
5498
|
const start = i + 1;
|
|
@@ -5525,7 +5533,7 @@ class Decoder extends Emitter {
|
|
|
5525
5533
|
}
|
|
5526
5534
|
tryParse(str) {
|
|
5527
5535
|
try {
|
|
5528
|
-
return JSON.parse(str, this.reviver);
|
|
5536
|
+
return JSON.parse(str, this.opts.reviver);
|
|
5529
5537
|
} catch (e) {
|
|
5530
5538
|
return false;
|
|
5531
5539
|
}
|
|
@@ -5587,6 +5595,9 @@ class BinaryReconstructor {
|
|
|
5587
5595
|
this.buffers = [];
|
|
5588
5596
|
}
|
|
5589
5597
|
}
|
|
5598
|
+
const isInteger = Number.isInteger || function(value2) {
|
|
5599
|
+
return typeof value2 === "number" && isFinite(value2) && Math.floor(value2) === value2;
|
|
5600
|
+
};
|
|
5590
5601
|
function isObject(value2) {
|
|
5591
5602
|
return Object.prototype.toString.call(value2) === "[object Object]";
|
|
5592
5603
|
}
|
|
@@ -6942,6 +6953,14 @@ class EngineServicesClient {
|
|
|
6942
6953
|
* the new token is picked up on every request — expired tokens no
|
|
6943
6954
|
* longer stick around.
|
|
6944
6955
|
*/
|
|
6956
|
+
/**
|
|
6957
|
+
* Socket origin without namespace or query, for gateways other than the
|
|
6958
|
+
* execution one. `wsUrl` already carries a token that may be stale when a
|
|
6959
|
+
* provider is in play, so callers append their own.
|
|
6960
|
+
*/
|
|
6961
|
+
get socketOrigin() {
|
|
6962
|
+
return this.wsUrl.split("?")[0];
|
|
6963
|
+
}
|
|
6945
6964
|
async resolveAccessToken() {
|
|
6946
6965
|
return this.accessToken;
|
|
6947
6966
|
}
|
|
@@ -8120,7 +8139,7 @@ const libVersion = (() => {
|
|
|
8120
8139
|
}
|
|
8121
8140
|
})();
|
|
8122
8141
|
const templatesDir = require$$2.join(__dirname, "..", "src", "cli", "templates");
|
|
8123
|
-
const createCommand = new Command2("create").argument("<project-name>", 'Name of the project to create (use "." for current directory)').option("-t, --template <template>", `Template (${TEMPLATES.join(", ")})`, "app").option("--beta", "Use beta engine libraries (@thatopen-platform/*-beta)").description("Scaffold a new ThatOpen app or cloud component project").action(async (projectName, opts) => {
|
|
8142
|
+
const createCommand = new Command2("create").argument("<project-name>", 'Name of the project to create (use "." for current directory)').option("-t, --template <template>", `Template (${TEMPLATES.join(", ")})`, "app").option("--beta", "Use beta engine libraries (@thatopen-platform/*-beta)").option("--history", "App only: include the revit-flow commit history panel, already wired").description("Scaffold a new ThatOpen app or cloud component project").action(async (projectName, opts) => {
|
|
8124
8143
|
const template = opts.template;
|
|
8125
8144
|
if (!TEMPLATES.includes(template)) {
|
|
8126
8145
|
console.error(`Unknown template "${opts.template}". Available: ${TEMPLATES.join(", ")}`);
|
|
@@ -8159,6 +8178,16 @@ The templates currently require the beta engine libraries. Public (non-beta) eng
|
|
|
8159
8178
|
if (isCloud) {
|
|
8160
8179
|
require$$3.renameSync(require$$2.join(targetDir, "_thatopen"), require$$2.join(targetDir, ".thatopen"));
|
|
8161
8180
|
}
|
|
8181
|
+
if (!isCloud) {
|
|
8182
|
+
const variant = require$$2.join(targetDir, "src", "main.history.ts");
|
|
8183
|
+
if (require$$3.existsSync(variant)) {
|
|
8184
|
+
if (opts.history) require$$3.renameSync(variant, require$$2.join(targetDir, "src", "main.ts"));
|
|
8185
|
+
else require$$3.rmSync(variant);
|
|
8186
|
+
}
|
|
8187
|
+
} else if (opts.history) {
|
|
8188
|
+
console.error("--history applies to the app template only.");
|
|
8189
|
+
process.exit(1);
|
|
8190
|
+
}
|
|
8162
8191
|
const pkgPath = require$$2.join(targetDir, "package.json");
|
|
8163
8192
|
const pkgContent = require$$3.readFileSync(pkgPath, "utf-8").replace(/\{\{PROJECT_NAME\}\}/g, packageName).replace(/\{\{VERSION\}\}/g, libVersion).replace(/"@thatopen\/services": "file:[^"]*"/, `"@thatopen/services": "^${libVersion}"`);
|
|
8164
8193
|
require$$3.writeFileSync(pkgPath, pkgContent);
|
|
@@ -8216,7 +8245,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js
|
|
|
8216
8245
|
JSZip uses the library pako released under the MIT license :
|
|
8217
8246
|
https://github.com/nodeca/pako/blob/main/LICENSE
|
|
8218
8247
|
*/
|
|
8219
|
-
(function(module2, exports
|
|
8248
|
+
(function(module2, exports) {
|
|
8220
8249
|
!function(e) {
|
|
8221
8250
|
module2.exports = e();
|
|
8222
8251
|
}(function() {
|
|
@@ -11733,6 +11762,38 @@ function discoveryFile() {
|
|
|
11733
11762
|
const appData = process.env.APPDATA || require$$2.join(node_os.homedir(), "AppData", "Roaming");
|
|
11734
11763
|
return require$$2.join(appData, "ThatOpen", "revit-addin.json");
|
|
11735
11764
|
}
|
|
11765
|
+
function tryDiscoverAddin() {
|
|
11766
|
+
const file = discoveryFile();
|
|
11767
|
+
if (!require$$3.existsSync(file)) return null;
|
|
11768
|
+
try {
|
|
11769
|
+
return JSON.parse(require$$3.readFileSync(file, "utf-8"));
|
|
11770
|
+
} catch {
|
|
11771
|
+
return null;
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
async function addinAlive() {
|
|
11775
|
+
const info = tryDiscoverAddin();
|
|
11776
|
+
if (!info) return false;
|
|
11777
|
+
try {
|
|
11778
|
+
const res = await fetch(`http://127.0.0.1:${info.port}/status`, {
|
|
11779
|
+
method: "POST",
|
|
11780
|
+
headers: { "Content-Type": "application/json", "X-RevitFlow-Token": info.token },
|
|
11781
|
+
body: "{}",
|
|
11782
|
+
signal: AbortSignal.timeout(4e3)
|
|
11783
|
+
});
|
|
11784
|
+
return res.ok;
|
|
11785
|
+
} catch {
|
|
11786
|
+
return false;
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
async function waitForAddin(timeoutMs) {
|
|
11790
|
+
const until = Date.now() + timeoutMs;
|
|
11791
|
+
while (Date.now() < until) {
|
|
11792
|
+
if (await addinAlive()) return true;
|
|
11793
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
11794
|
+
}
|
|
11795
|
+
return false;
|
|
11796
|
+
}
|
|
11736
11797
|
function discoverAddin() {
|
|
11737
11798
|
const file = discoveryFile();
|
|
11738
11799
|
if (!require$$3.existsSync(file)) {
|
|
@@ -11789,6 +11850,28 @@ async function configureAddin(apiUrl, accessToken) {
|
|
|
11789
11850
|
key: accessToken
|
|
11790
11851
|
});
|
|
11791
11852
|
}
|
|
11853
|
+
function isRevitRunning() {
|
|
11854
|
+
if (process.platform !== "win32") return false;
|
|
11855
|
+
try {
|
|
11856
|
+
return require$$1.execSync('tasklist /FI "IMAGENAME eq Revit.exe" /NH', { encoding: "utf-8" }).toLowerCase().includes("revit.exe");
|
|
11857
|
+
} catch {
|
|
11858
|
+
return false;
|
|
11859
|
+
}
|
|
11860
|
+
}
|
|
11861
|
+
function startRevit() {
|
|
11862
|
+
const roots = ["C:\\Program Files\\Autodesk"];
|
|
11863
|
+
for (const root of roots) {
|
|
11864
|
+
if (!require$$3.existsSync(root)) continue;
|
|
11865
|
+
for (const dir of require$$3.readdirSync(root)) {
|
|
11866
|
+
if (!/^Revit \d{4}$/.test(dir)) continue;
|
|
11867
|
+
const exe = require$$2.join(root, dir, "Revit.exe");
|
|
11868
|
+
if (!require$$3.existsSync(exe)) continue;
|
|
11869
|
+
require$$1.spawn(exe, [], { detached: true, stdio: "ignore" }).unref();
|
|
11870
|
+
return;
|
|
11871
|
+
}
|
|
11872
|
+
}
|
|
11873
|
+
console.log(" Could not find Revit.exe. Start Revit yourself and this will pick it up.");
|
|
11874
|
+
}
|
|
11792
11875
|
const REVIT_ADDIN_PACKAGE = "@thatopen-platform/flow-revit";
|
|
11793
11876
|
const revitCommand = new Command2("revit").description(
|
|
11794
11877
|
"Revit collaboration — drive the That Open Revit add-in from the CLI"
|
|
@@ -11815,6 +11898,64 @@ revitCommand.command("inspect").description("Check whether a .rvt is already a w
|
|
|
11815
11898
|
console.log("Ask the user for consent, then run publish-central with --convert.");
|
|
11816
11899
|
}
|
|
11817
11900
|
});
|
|
11901
|
+
revitCommand.command("share").description("Share a .rvt with your team: install if needed, publish the central, open your local").requiredOption("--file <path>", "Absolute path to the .rvt on this machine").requiredOption("--project <id>", "Platform project id (or the dashboard URL)").option("--doc <name>", "Short name for the shared central (default: from the file name)").action(async (opts) => {
|
|
11902
|
+
const cfg = requireResolvedConfig();
|
|
11903
|
+
const file = require$$2.resolve(opts.file);
|
|
11904
|
+
if (!require$$3.existsSync(file)) {
|
|
11905
|
+
console.error(`No such file: ${file}`);
|
|
11906
|
+
process.exit(1);
|
|
11907
|
+
}
|
|
11908
|
+
const project = opts.project.match(/projects\/([a-f0-9]{24})/i)?.[1] ?? opts.project;
|
|
11909
|
+
const doc = opts.doc ?? require$$2.basename(file, ".rvt").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
11910
|
+
console.log(`Sharing "${require$$2.basename(file)}" as "${doc}" in project ${project}.
|
|
11911
|
+
`);
|
|
11912
|
+
if (!await addinAlive()) {
|
|
11913
|
+
if (!isRevitRunning()) {
|
|
11914
|
+
console.log("[1/5] Installing the Revit add-in...");
|
|
11915
|
+
const client = new EngineServicesClient(cfg.accessToken, cfg.apiUrl);
|
|
11916
|
+
const { dir, cleanup } = await fetchPluginPackage(client, REVIT_ADDIN_PACKAGE, "latest");
|
|
11917
|
+
try {
|
|
11918
|
+
const installer = require$$2.join(dir, "install.ps1");
|
|
11919
|
+
if (!require$$3.existsSync(installer)) throw new Error("The package has no install.ps1.");
|
|
11920
|
+
require$$1.execFileSync("powershell", ["-ExecutionPolicy", "Bypass", "-File", installer], {
|
|
11921
|
+
stdio: "inherit"
|
|
11922
|
+
});
|
|
11923
|
+
} finally {
|
|
11924
|
+
cleanup();
|
|
11925
|
+
}
|
|
11926
|
+
console.log("[2/5] Starting Revit...");
|
|
11927
|
+
startRevit();
|
|
11928
|
+
} else {
|
|
11929
|
+
console.log("[1/5] Waiting for Revit...");
|
|
11930
|
+
}
|
|
11931
|
+
if (!await waitForAddin(3e5)) {
|
|
11932
|
+
console.error(
|
|
11933
|
+
'\nRevit did not report in. If it is open, the add-in may not be installed:\n close Revit, run "thatopen revit install", and start it again.'
|
|
11934
|
+
);
|
|
11935
|
+
process.exit(1);
|
|
11936
|
+
}
|
|
11937
|
+
} else {
|
|
11938
|
+
console.log("[1/5] Revit is open and the add-in is answering.");
|
|
11939
|
+
}
|
|
11940
|
+
await configureAddin(cfg.apiUrl, cfg.accessToken);
|
|
11941
|
+
console.log("[3/5] Checking the file...");
|
|
11942
|
+
const info = await callAddin("inspect", { file });
|
|
11943
|
+
console.log(
|
|
11944
|
+
info.isCentral ? " Already a workshared central. Publishing a copy of it." : " Not a central yet. Converting a COPY. Your file is not modified."
|
|
11945
|
+
);
|
|
11946
|
+
console.log("[4/5] Publishing to the platform (Revit is saving the central, this takes a bit)...");
|
|
11947
|
+
const pub = await callAddin("publish-central", { project, doc, file, convert: true });
|
|
11948
|
+
console.log("[5/5] Creating and opening your local...");
|
|
11949
|
+
const joined = await callAddin("join", { project, doc });
|
|
11950
|
+
console.log(`
|
|
11951
|
+
Shared. Central "${doc}" is v${pub.version} in project ${project}.`);
|
|
11952
|
+
console.log(`Your local, now open in Revit:
|
|
11953
|
+
${joined.local}`);
|
|
11954
|
+
console.log(`
|
|
11955
|
+
Teammates join with:
|
|
11956
|
+
thatopen revit join --project ${project} --doc ${doc}`);
|
|
11957
|
+
console.log(`From here, Revit's own Synchronize with Central publishes to the team.`);
|
|
11958
|
+
});
|
|
11818
11959
|
revitCommand.command("publish-central").description("Turn a .rvt into a shared central and upload it to a project").requiredOption("--project <id>", "Platform project id").requiredOption("--doc <name>", "A short name for this central (e.g. tower-central)").requiredOption("--file <path>", "Absolute path to the source .rvt on this machine").option("--convert", "Allow converting a non-central .rvt into one (on a copy; original untouched)").action(async (opts) => {
|
|
11819
11960
|
await connect();
|
|
11820
11961
|
console.log(`Publishing "${opts.file}" as central "${opts.doc}"...`);
|
package/dist/core/client.d.ts
CHANGED
|
@@ -207,6 +207,12 @@ export declare class EngineServicesClient {
|
|
|
207
207
|
* the new token is picked up on every request — expired tokens no
|
|
208
208
|
* longer stick around.
|
|
209
209
|
*/
|
|
210
|
+
/**
|
|
211
|
+
* Socket origin without namespace or query, for gateways other than the
|
|
212
|
+
* execution one. `wsUrl` already carries a token that may be stale when a
|
|
213
|
+
* provider is in play, so callers append their own.
|
|
214
|
+
*/
|
|
215
|
+
protected get socketOrigin(): string;
|
|
210
216
|
protected resolveAccessToken(): Promise<string>;
|
|
211
217
|
/**
|
|
212
218
|
* Protected extension hook for subclasses (e.g. `PlatformClient`) that
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { EngineServicesClient, EngineServicesClientProps } from './client';
|
|
2
2
|
import { Project, ProjectData } from '../types/projects';
|
|
3
|
+
import { MarkNotificationsReadResultDto, NotificationPageDto, NotificationSubscriptionView } from '../types/notifications';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* What arrived on the socket. One callback for all three because a bell
|
|
7
|
+
* reacts the same way to each: refresh the badge, and the list if open.
|
|
8
|
+
*
|
|
9
|
+
* `allRead` is one event for the whole sweep rather than one per
|
|
10
|
+
* notification, so do not expect an id on it.
|
|
11
|
+
*/
|
|
12
|
+
export type LiveNotificationEvent = {
|
|
13
|
+
type: 'created';
|
|
14
|
+
id: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'read';
|
|
17
|
+
id: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'allRead';
|
|
20
|
+
batch: number;
|
|
21
|
+
};
|
|
22
|
+
/** Per-automation opt-in. `failures` skips started events entirely. */
|
|
23
|
+
export type NotificationSubscriptionFilterInput = 'all' | 'failures';
|
|
24
|
+
export interface NotificationSubscriptionInput {
|
|
25
|
+
filter?: NotificationSubscriptionFilterInput;
|
|
26
|
+
/** Overrides the account-level channel setting for this automation only. */
|
|
27
|
+
channels?: {
|
|
28
|
+
email?: boolean;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
4
31
|
/** Scope by which a permission was granted (or `'none'` if denied). */
|
|
5
32
|
export type PermissionScope = 'global' | 'project' | 'entity' | 'none';
|
|
6
33
|
/** Result of a single permission check. */
|
|
@@ -110,4 +137,116 @@ export declare class PlatformClient extends EngineServicesClient {
|
|
|
110
137
|
* single round-trip; results come back in the same order as `checks`.
|
|
111
138
|
*/
|
|
112
139
|
checkPermissionBatch(checks: PermissionCheckEntry[]): Promise<PermissionCheckResult[]>;
|
|
140
|
+
/**
|
|
141
|
+
* Lists the signed-in user's notifications, newest first.
|
|
142
|
+
*
|
|
143
|
+
* Scoped to whoever the bearer token belongs to — an app cannot read
|
|
144
|
+
* anyone else's. Muted notifications are included: muting silences the
|
|
145
|
+
* badge and the delivery channels, it does not hide the record.
|
|
146
|
+
*
|
|
147
|
+
* Paginate by passing the previous response's `nextCursor` back in; it is
|
|
148
|
+
* opaque, so do not build one by hand. A null `nextCursor` means the last
|
|
149
|
+
* page.
|
|
150
|
+
*
|
|
151
|
+
* @example Walk every page:
|
|
152
|
+
* ```ts
|
|
153
|
+
* let cursor: string | undefined;
|
|
154
|
+
* do {
|
|
155
|
+
* const page = await client.getNotifications({ cursor });
|
|
156
|
+
* render(page.items);
|
|
157
|
+
* cursor = page.nextCursor ?? undefined;
|
|
158
|
+
* } while (cursor);
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
getNotifications(params?: {
|
|
162
|
+
cursor?: string;
|
|
163
|
+
limit?: number;
|
|
164
|
+
}): Promise<NotificationPageDto>;
|
|
165
|
+
/**
|
|
166
|
+
* Number of unread notifications, excluding muted ones. This is the bell
|
|
167
|
+
* badge count, so it is cheap to poll.
|
|
168
|
+
*/
|
|
169
|
+
getUnreadNotificationCount(): Promise<number>;
|
|
170
|
+
/**
|
|
171
|
+
* Marks specific notifications as read. Ids the caller does not own are
|
|
172
|
+
* ignored rather than rejected, so `updated` can be lower than the number
|
|
173
|
+
* passed in.
|
|
174
|
+
*/
|
|
175
|
+
markNotificationsRead(notificationIds: string[]): Promise<MarkNotificationsReadResultDto>;
|
|
176
|
+
/** Marks every unread notification as read in one call. */
|
|
177
|
+
markAllNotificationsRead(): Promise<MarkNotificationsReadResultDto>;
|
|
178
|
+
/**
|
|
179
|
+
* The automations this user has subscribed to. Nobody is subscribed by
|
|
180
|
+
* default, so an empty list is the normal state.
|
|
181
|
+
*/
|
|
182
|
+
getNotificationSubscriptions(): Promise<NotificationSubscriptionView[]>;
|
|
183
|
+
/**
|
|
184
|
+
* Removes this user's subscription to one automation.
|
|
185
|
+
*
|
|
186
|
+
* Unlike subscribing, which happens through the project routes, this works
|
|
187
|
+
* for any automation the user is subscribed to and keeps working after the
|
|
188
|
+
* automation itself is gone — opting out must never be the thing that
|
|
189
|
+
* fails.
|
|
190
|
+
*/
|
|
191
|
+
unsubscribeFromAutomation(hookId: string): Promise<{
|
|
192
|
+
unsubscribed: boolean;
|
|
193
|
+
}>;
|
|
194
|
+
/**
|
|
195
|
+
* Subscribes the signed-in user to one project automation's runs.
|
|
196
|
+
*
|
|
197
|
+
* Nobody is subscribed by default, so this is what makes an automation
|
|
198
|
+
* produce notifications for this user at all. Subscribing again is
|
|
199
|
+
* harmless: it updates the existing subscription rather than duplicating.
|
|
200
|
+
*
|
|
201
|
+
* Read-level access to the project is enough. Someone who can see an
|
|
202
|
+
* automation can follow it without being able to change it.
|
|
203
|
+
*
|
|
204
|
+
* @example Follow only the failures, and email me about them:
|
|
205
|
+
* ```ts
|
|
206
|
+
* await client.subscribeToAutomation(projectId, hookId, {
|
|
207
|
+
* filter: 'failures',
|
|
208
|
+
* channels: { email: true },
|
|
209
|
+
* });
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
subscribeToAutomation(projectId: string, hookId: string, input?: NotificationSubscriptionInput): Promise<{
|
|
213
|
+
subscribed: true;
|
|
214
|
+
}>;
|
|
215
|
+
/**
|
|
216
|
+
* Changes an existing subscription. Only the fields passed are touched, so
|
|
217
|
+
* sending `channels` alone leaves the filter as it was.
|
|
218
|
+
*
|
|
219
|
+
* Throws if the user is not subscribed; use {@link subscribeToAutomation}
|
|
220
|
+
* to create one.
|
|
221
|
+
*/
|
|
222
|
+
updateAutomationSubscription(projectId: string, hookId: string, changes: NotificationSubscriptionInput): Promise<{
|
|
223
|
+
updated: true;
|
|
224
|
+
}>;
|
|
225
|
+
/**
|
|
226
|
+
* Listens for this user's notifications in real time.
|
|
227
|
+
*
|
|
228
|
+
* Unlike {@link EngineServicesClient.onExecutionProgress}, which follows one
|
|
229
|
+
* execution and closes when it ends, this stays connected for the session:
|
|
230
|
+
* the server puts the socket in a room for the signed-in account and pushes
|
|
231
|
+
* anything addressed to them.
|
|
232
|
+
*
|
|
233
|
+
* The events carry ids rather than the notifications themselves, so treat
|
|
234
|
+
* them as a signal to refresh. That keeps a burst cheap and means the
|
|
235
|
+
* server is never the source of a stale render.
|
|
236
|
+
*
|
|
237
|
+
* Requires a user JWT. An API access token is rejected by the gateway,
|
|
238
|
+
* because it identifies a token rather than a person.
|
|
239
|
+
*
|
|
240
|
+
* @returns a function that disconnects. Call it on unmount.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* const stop = await client.onNotification((event) => {
|
|
245
|
+
* if (event.type === 'created') refreshBell();
|
|
246
|
+
* });
|
|
247
|
+
* // later
|
|
248
|
+
* stop();
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
onNotification(onEvent: (event: LiveNotificationEvent) => void): Promise<() => void>;
|
|
113
252
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|