@jsm-mit/sultana-assets-rust-package 0.3.0 → 0.4.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 CHANGED
@@ -34,7 +34,7 @@ One class per part of the canister:
34
34
  | `AssetsRustUploadsActor` | chunked session, single-shot upload, `uploadBytesAsyncUnsafe` | any signed-in caller |
35
35
  | `AssetsRustSalonActor` | `upload_image`, `delete_salon_asset`, transcoding and render reservations | the salon's owner — core decides |
36
36
  | `AssetsRustWorkerActor` | system upload of a transcoded result under a reserved id | listed transcoding workers |
37
- | `AssetsRustAssetsActor` | `get_asset_info`, `list_assets`, `http_request`, `assetUrl` | anybody |
37
+ | `AssetsRustAssetsActor` | `get_asset_info`, `list_assets`, `http_request`, `assetUrl` | anybody; `list_assets` only the canister's controllers and the core seat |
38
38
  | `AssetsRustAvatarActor` | `upload_my_avatar`, `delete_my_avatar` (own avatar only, profile required — core `isUser` —, no quota; 64 KB / 512 px, re-encoded as JPEG), `get_avatar`, `/avatar/<principal>`, `avatarUrl` | writes: the caller for themselves; reads: anybody |
39
39
 
40
40
  Every method is `…AsyncUnsafe` and throws one of three errors, like the other wrapper
@@ -57,14 +57,16 @@ types are exported too, and `./declarations/*` gives the generated files.
57
57
  `src/` is browser-safe — the same package serves the biz panel, the playground and the
58
58
  transcoding worker.
59
59
 
60
- Scripts: `build`, `typecheck`, `sync-declarations`, `test-suite`, `test-smoke`, `test-upgrade`, `redeploy`, `sandbox`
60
+ Scripts: `build`, `typecheck`, `sync-declarations`, `test-suite`, `test-smoke`, `test-upgrade`,
61
+ `redeploy`, `sandbox`
61
62
  (an interactive console: `npm run sandbox -- <canister-id>`, caller from `IDENTITY_PEM` in
62
63
  `.env.sandbox` or anonymous), `stamp-canister`, `publish-public`. See `CLAUDE.md` for who runs what.
63
64
 
64
65
  ## Integration tests
65
66
 
66
- `npm run test-suite` reinstalls the current `../sultana-assets-rust` build into the crafting
67
- table's `short-living` slot (lease 3 minutes) and runs `tests/test-suite.ts` there. No
67
+ All tests run on the LOCAL replica — no cycles, no shared slot. `npm run test-suite`
68
+ reinstalls the current `../sultana-assets-rust` build into a local canister and runs
69
+ `tests/test-suite.ts` there. No
68
70
  `.env`, no PEMs: every identity is generated per run. A fresh canister has no core canister,
69
71
  so the suite claims that claim-once seat for a generated identity and plays core itself.
70
72
 
@@ -73,6 +75,12 @@ read per domain (worker list, a few-byte single-shot upload read back over HTTP,
73
75
  result, a salon upload refused by the stand-in core), one replication wait, ≈ 30 s.
74
76
  `publish-public` runs it too.
75
77
 
78
+ `npm run test-upgrade` seeds the canister, upgrades it in the middle of the run and checks
79
+ that the stored data reads back identical. `scripts/local-canister.sh`
80
+ builds `../sultana-assets-rust` with cargo and reinstalls it into a canister of the dfx project
81
+ in `local/` (its id stays under `local/.dfx`); the upgrade test upgrades that canister. The
82
+ local replica must run (`dfx start --background`); the scripts never start or stop it.
83
+
76
84
  What the suite covers: the core seat and the worker list; chunked and single-shot uploads
77
85
  with every owner and size check; reads, HTTP 200/206/404, certification header and
78
86
  streaming; the worker's system upload with a poster; `delete_asset` taking the thumb along;
@@ -85,7 +93,8 @@ the suite never touches it.
85
93
 
86
94
  Bump the version by hand, commit, push, then `npm run publish-public`. It refuses unless this
87
95
  repo and `../sultana-assets-rust` are on a clean, pushed `master` and the version is new on
88
- npm; it reinstalls the canister's master into `short-living` (lease 2), checks that its
96
+ npm; it reinstalls the canister's master into the canister on the LOCAL replica
97
+ (`scripts/local-canister.sh`; the replica must run), checks that its
89
98
  `candid:service` equals `declarations/sultana-assets-rust/sultana-assets-rust.did` as dfx
90
99
  rewrites it (the hand-written .did is normalized by a local `dfx build --check` first), runs
91
100
  the smoke test (`tests/test-smoke.ts`) on that same deploy, stamps
@@ -25,10 +25,13 @@ export declare class ActorBase {
25
25
  protected host: string;
26
26
  protected actor: ActorSubclass<_SERVICE>;
27
27
  protected agent: HttpAgent;
28
+ private ready;
28
29
  /**
29
30
  * @param canisterId - The sultana-assets-rust canister to talk to.
30
31
  * @param identity - The caller; left out = anonymous (enough for the reads).
31
- * @param host - The IC API host; default https://icp0.io.
32
+ * @param host - The IC API host; default https://icp0.io. A local replica
33
+ * (`http://127.0.0.1:4943`) works too: for a local host — and only then — the agent
34
+ * fetches the replica's root key before the first call.
32
35
  */
33
36
  constructor(canisterId: string, identity?: Identity | undefined, host?: string);
34
37
  private initAgentAndActor;
@@ -1 +1 @@
1
- {"version":3,"file":"actor-base.d.ts","sourceRoot":"","sources":["../src/actor-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAS,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gEAAgE,CAAC;AAG/F,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACtB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAC;CACrB;AAKD;;;;;;;;;GASG;AACH,qBAAa,SAAS;IASN,SAAS,CAAC,UAAU,EAAE,MAAM;IAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAAE,SAAS,CAAC,IAAI,EAAE,MAAM;IAR/F,SAAS,CAAC,KAAK,EAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC1C,SAAS,CAAC,KAAK,EAAG,SAAS,CAAC;IAE5B;;;;OAIG;gBACmB,UAAU,EAAE,MAAM,EAAY,QAAQ,CAAC,EAAE,QAAQ,YAAA,EAAY,IAAI,GAAE,MAAqB;IAI9G,OAAO,CAAC,iBAAiB;IAWzB;;;;OAIG;IACI,iBAAiB,IAAI,IAAI;IAIhC,iFAAiF;cACjE,0BAA0B,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAqBpF,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO;IASpD,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;CAU1D"}
1
+ {"version":3,"file":"actor-base.d.ts","sourceRoot":"","sources":["../src/actor-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,KAAK,QAAQ,EAAS,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gEAAgE,CAAC;AAG/F,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACtB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;;;;GASG;AACH,qBAAa,SAAS;IAYN,SAAS,CAAC,UAAU,EAAE,MAAM;IAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,QAAQ;IAAE,SAAS,CAAC,IAAI,EAAE,MAAM;IAX/F,SAAS,CAAC,KAAK,EAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC1C,SAAS,CAAC,KAAK,EAAG,SAAS,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAoC;IAEjD;;;;;;OAMG;gBACmB,UAAU,EAAE,MAAM,EAAY,QAAQ,CAAC,EAAE,QAAQ,YAAA,EAAY,IAAI,GAAE,MAAqB;IAI9G,OAAO,CAAC,iBAAiB;IAYzB;;;;OAIG;IACI,iBAAiB,IAAI,IAAI;IAIhC,iFAAiF;cACjE,0BAA0B,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAuBpF,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO;IAcpD,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;CAU1D"}
@@ -1,6 +1,9 @@
1
1
  import { HttpAgent, Actor } from "@icp-sdk/core/agent";
2
2
  import { idlFactory } from "../declarations/sultana-assets-rust/sultana-assets-rust.did.js";
3
3
  import { DEFAULT_HOST } from "./constants.js";
4
+ function isLocalHost(host) {
5
+ return /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?\/?$/.test(host);
6
+ }
4
7
  // IC error code of an explicit trap (ic0.trap — every ic_cdk::trap and every Rust panic).
5
8
  const TRAP_ERROR_CODE = "IC0503";
6
9
  /**
@@ -19,10 +22,13 @@ export class ActorBase {
19
22
  host;
20
23
  actor;
21
24
  agent;
25
+ ready = Promise.resolve();
22
26
  /**
23
27
  * @param canisterId - The sultana-assets-rust canister to talk to.
24
28
  * @param identity - The caller; left out = anonymous (enough for the reads).
25
- * @param host - The IC API host; default https://icp0.io.
29
+ * @param host - The IC API host; default https://icp0.io. A local replica
30
+ * (`http://127.0.0.1:4943`) works too: for a local host — and only then — the agent
31
+ * fetches the replica's root key before the first call.
26
32
  */
27
33
  constructor(canisterId, identity, host = DEFAULT_HOST) {
28
34
  this.canisterId = canisterId;
@@ -35,6 +41,7 @@ export class ActorBase {
35
41
  host: this.host,
36
42
  identity: this.identity,
37
43
  });
44
+ this.ready = isLocalHost(this.host) ? this.agent.fetchRootKey().then(() => undefined) : Promise.resolve();
38
45
  this.actor = Actor.createActor(idlFactory, {
39
46
  agent: this.agent,
40
47
  canisterId: this.canisterId,
@@ -50,6 +57,7 @@ export class ActorBase {
50
57
  }
51
58
  /** Runs one canister call and translates its failure (see the class comment). */
52
59
  async executeFunctionAsyncUnsafe(fnAsync) {
60
+ await this.ready;
53
61
  try {
54
62
  return await fnAsync();
55
63
  }
@@ -65,12 +73,15 @@ export class ActorBase {
65
73
  throw new Error("CriticalCanisterError", { cause: { logs: null, rawError: err } });
66
74
  }
67
75
  }
68
- // True if `error` is an IC reject caused by an inspect-stage refusal. sultana-assets-rust
69
- // has no inspect_message today; kept so the error vocabulary stays the same as in the
70
- // other wrapper packages. When the canister gets one, check the text it produces.
76
+ // True if `error` is an IC reject caused by an inspect-stage refusal. The canister's
77
+ // inspect_message (since 2026-10-25) refuses by not accepting, which the replica reports as
78
+ // "Canister rejected the message" (IC0406); older replicas said "... refused message".
71
79
  isInspectionRejection(error) {
72
80
  const rejectText = error?.cause?.code?.rejectMessage || error?.message || "";
73
- return rejectText.includes("canister_inspect_message explicitly refused message");
81
+ return (rejectText.includes("Canister rejected the message") ||
82
+ rejectText.includes("IC0406") ||
83
+ rejectText.includes("canister_inspect_message explicitly refused message") ||
84
+ rejectText.includes("canister_inspect_message implicitly refused message"));
74
85
  }
75
86
  // The trap text when `error` is a trap of the canister, otherwise null. The replica
76
87
  // reports it as "... Canister called `ic0.trap` with message: '<text>'.\nConsider ...";
@@ -1,6 +1,7 @@
1
1
  import { ActorBase } from "../actor-base.js";
2
2
  import type { AssetInfoView, HttpGetInput, HttpResponseView } from "../interfaces.js";
3
- /** Reads: what is stored and how it is served. All queries — anonymous callers are fine. */
3
+ /** Reads: what is stored and how it is served. All queries — anonymous callers are fine,
4
+ * except `listAssetsAsyncUnsafe`: the canister's controllers and the core seat only. */
4
5
  export declare class AssetsRustAssetsActor extends ActorBase {
5
6
  /**
6
7
  * @param assetId - The asset.
@@ -13,7 +14,8 @@ export declare class AssetsRustAssetsActor extends ActorBase {
13
14
  getAssetInfoAsyncUnsafe(assetId: string): Promise<AssetInfoView | null>;
14
15
  /**
15
16
  * Every asset physically stored — the raw storage view, independent of any salon library:
16
- * thumbnails, posters and orphans included.
17
+ * thumbnails, posters and orphans included. The canister's controllers and the core seat
18
+ * only; anybody else gets a CanisterError ("list_assets refused: controllers only").
17
19
  * @returns A Promise that resolves to every stored asset.
18
20
  * @throws CanisterError - The canister trapped.
19
21
  * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
@@ -1 +1 @@
1
- {"version":3,"file":"assets-actor.d.ts","sourceRoot":"","sources":["../../src/actors/assets-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,4FAA4F;AAC5F,qBAAa,qBAAsB,SAAQ,SAAS;IAChD;;;;;;;OAOG;IACU,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKpF;;;;;;;OAOG;IACU,qBAAqB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAK9D;;;;;;;;;;OAUG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,YAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAYzG,0EAA0E;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAG3C"}
1
+ {"version":3,"file":"assets-actor.d.ts","sourceRoot":"","sources":["../../src/actors/assets-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGtF;wFACwF;AACxF,qBAAa,qBAAsB,SAAQ,SAAS;IAChD;;;;;;;OAOG;IACU,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKpF;;;;;;;;OAQG;IACU,qBAAqB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;IAK9D;;;;;;;;;;OAUG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,YAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAYzG,0EAA0E;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAG3C"}
@@ -1,7 +1,8 @@
1
1
  import { ActorBase } from "../actor-base.js";
2
2
  import { assetUrl } from "../constants.js";
3
3
  import { mapAssetInfoView, mapHttpResponseView } from "../mappers.js";
4
- /** Reads: what is stored and how it is served. All queries — anonymous callers are fine. */
4
+ /** Reads: what is stored and how it is served. All queries — anonymous callers are fine,
5
+ * except `listAssetsAsyncUnsafe`: the canister's controllers and the core seat only. */
5
6
  export class AssetsRustAssetsActor extends ActorBase {
6
7
  /**
7
8
  * @param assetId - The asset.
@@ -17,7 +18,8 @@ export class AssetsRustAssetsActor extends ActorBase {
17
18
  }
18
19
  /**
19
20
  * Every asset physically stored — the raw storage view, independent of any salon library:
20
- * thumbnails, posters and orphans included.
21
+ * thumbnails, posters and orphans included. The canister's controllers and the core seat
22
+ * only; anybody else gets a CanisterError ("list_assets refused: controllers only").
21
23
  * @returns A Promise that resolves to every stored asset.
22
24
  * @throws CanisterError - The canister trapped.
23
25
  * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
@@ -2,7 +2,9 @@ import { ActorBase } from "../actor-base.js";
2
2
  import type { ChunkedUploadOptions, StartUploadInput, UploadAssetInput } from "../interfaces.js";
3
3
  /**
4
4
  * Generic uploads, not tied to a salon: a chunked session (start → chunks → finish) or a
5
- * single-shot upload. Any signed-in caller may upload; the session belongs to its starter.
5
+ * single-shot upload. Only an admin of the group may upload (the canister asks core's
6
+ * `isAdmin`; today the cockpit's APK), at most 64 MiB per session and three open sessions per
7
+ * uploader (ASSETS_RUST_LIMITS); the session belongs to its starter.
6
8
  * Salon-library uploads go through {@link AssetsRustSalonActor} instead.
7
9
  */
8
10
  export declare class AssetsRustUploadsActor extends ActorBase {
@@ -11,7 +13,8 @@ export declare class AssetsRustUploadsActor extends ActorBase {
11
13
  * after 30 minutes.
12
14
  * @param input - Name, content type and the number of chunks that will follow.
13
15
  * @returns A Promise that resolves to the new asset id (the session id).
14
- * @throws CanisterError - The canister trapped: anonymous caller, totalChunks 0.
16
+ * @throws CanisterError - The canister trapped: anonymous caller, totalChunks 0, upload
17
+ * too large, not an admin of the group (`upload refused`), too many open sessions.
15
18
  * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
16
19
  * @throws CriticalCanisterError - Transport failure or an unreachable canister.
17
20
  */
@@ -52,7 +55,8 @@ export declare class AssetsRustUploadsActor extends ActorBase {
52
55
  * Single-shot upload of a small file.
53
56
  * @param input - Name, content type and at most ASSETS_RUST_LIMITS.chunkSizeBytes of data.
54
57
  * @returns A Promise that resolves to the new asset id.
55
- * @throws CanisterError - The canister trapped: anonymous caller, file too large.
58
+ * @throws CanisterError - The canister trapped: anonymous caller, file too large, not an
59
+ * admin of the group (`upload refused`).
56
60
  * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
57
61
  * @throws CriticalCanisterError - Transport failure or an unreachable canister.
58
62
  */
@@ -1 +1 @@
1
- {"version":3,"file":"uploads-actor.d.ts","sourceRoot":"","sources":["../../src/actors/uploads-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEjG;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACjD;;;;;;;;OAQG;IACU,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7E;;;;;;;;;;OAUG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpG;;;;;;;;OAQG;IACU,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAItE;;;;;;;;OAQG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE;;;;;;;OAOG;IACU,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7E;;;;;;;;;;;OAWG;IACU,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;CAgBjJ"}
1
+ {"version":3,"file":"uploads-actor.d.ts","sourceRoot":"","sources":["../../src/actors/uploads-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEjG;;;;;;GAMG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACjD;;;;;;;;;OASG;IACU,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7E;;;;;;;;;;OAUG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpG;;;;;;;;OAQG;IACU,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAItE;;;;;;;;OAQG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE;;;;;;;;OAQG;IACU,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7E;;;;;;;;;;;OAWG;IACU,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;CAgBjJ"}
@@ -2,7 +2,9 @@ import { ActorBase } from "../actor-base.js";
2
2
  import { splitIntoChunks } from "../constants.js";
3
3
  /**
4
4
  * Generic uploads, not tied to a salon: a chunked session (start → chunks → finish) or a
5
- * single-shot upload. Any signed-in caller may upload; the session belongs to its starter.
5
+ * single-shot upload. Only an admin of the group may upload (the canister asks core's
6
+ * `isAdmin`; today the cockpit's APK), at most 64 MiB per session and three open sessions per
7
+ * uploader (ASSETS_RUST_LIMITS); the session belongs to its starter.
6
8
  * Salon-library uploads go through {@link AssetsRustSalonActor} instead.
7
9
  */
8
10
  export class AssetsRustUploadsActor extends ActorBase {
@@ -11,7 +13,8 @@ export class AssetsRustUploadsActor extends ActorBase {
11
13
  * after 30 minutes.
12
14
  * @param input - Name, content type and the number of chunks that will follow.
13
15
  * @returns A Promise that resolves to the new asset id (the session id).
14
- * @throws CanisterError - The canister trapped: anonymous caller, totalChunks 0.
16
+ * @throws CanisterError - The canister trapped: anonymous caller, totalChunks 0, upload
17
+ * too large, not an admin of the group (`upload refused`), too many open sessions.
15
18
  * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
16
19
  * @throws CriticalCanisterError - Transport failure or an unreachable canister.
17
20
  */
@@ -60,7 +63,8 @@ export class AssetsRustUploadsActor extends ActorBase {
60
63
  * Single-shot upload of a small file.
61
64
  * @param input - Name, content type and at most ASSETS_RUST_LIMITS.chunkSizeBytes of data.
62
65
  * @returns A Promise that resolves to the new asset id.
63
- * @throws CanisterError - The canister trapped: anonymous caller, file too large.
66
+ * @throws CanisterError - The canister trapped: anonymous caller, file too large, not an
67
+ * admin of the group (`upload refused`).
64
68
  * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
65
69
  * @throws CriticalCanisterError - Transport failure or an unreachable canister.
66
70
  */
@@ -12,6 +12,10 @@ export declare const ASSETS_RUST_LIMITS: {
12
12
  readonly maxRangeBytes: 1800000;
13
13
  /** Width of the thumbnail `upload_image` generates (never upscaled). */
14
14
  readonly thumbnailWidth: 128;
15
+ /** Generic uploads (`start_upload`, `upload_asset`; admins of the group only): the most
16
+ * bytes one session may announce, and the open sessions one uploader may hold. */
17
+ readonly maxGenericUploadBytes: number;
18
+ readonly maxOpenSessionsPerOwner: 3;
15
19
  /** An unfinished upload session is purged after this long (30 min). */
16
20
  readonly sessionTtlMs: number;
17
21
  /** The only audio content type `reserve_render` takes as the music track. */
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;IAC3B;iFAC6E;;IAE7E,yDAAyD;;IAEzD,wEAAwE;;IAExE,uEAAuE;;IAEvE,6EAA6E;;IAE7E,6FAA6F;;;;;IAK7F;uDACmD;;;CAG7C,CAAC;AAEX;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;oCACoC;AACpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,GAAE,MAA0C,GAAG,UAAU,EAAE,CAY3H"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;IAC3B;iFAC6E;;IAE7E,yDAAyD;;IAEzD,wEAAwE;;IAExE;uFACmF;;;IAGnF,uEAAuE;;IAEvE,6EAA6E;;IAE7E,6FAA6F;;;;;IAK7F;uDACmD;;;CAG7C,CAAC;AAEX;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;oCACoC;AACpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,GAAE,MAA0C,GAAG,UAAU,EAAE,CAY3H"}
package/dist/constants.js CHANGED
@@ -12,6 +12,10 @@ export const ASSETS_RUST_LIMITS = {
12
12
  maxRangeBytes: 1_800_000,
13
13
  /** Width of the thumbnail `upload_image` generates (never upscaled). */
14
14
  thumbnailWidth: 128,
15
+ /** Generic uploads (`start_upload`, `upload_asset`; admins of the group only): the most
16
+ * bytes one session may announce, and the open sessions one uploader may hold. */
17
+ maxGenericUploadBytes: 64 * 1024 * 1024,
18
+ maxOpenSessionsPerOwner: 3,
15
19
  /** An unfinished upload session is purged after this long (30 min). */
16
20
  sessionTtlMs: 30 * 60 * 1000,
17
21
  /** The only audio content type `reserve_render` takes as the music track. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsm-mit/sultana-assets-rust-package",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Wrapper TypeScript package for the sultana-assets-rust canister: chunked and single-shot uploads, salon images and transcoding reservations, the worker upload path, reads and asset URLs.",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -25,9 +25,9 @@
25
25
  "publish-public": "bash scripts/publish-public.sh",
26
26
  "typecheck": "tsc --noEmit -p tsconfig.tests.json",
27
27
  "sync-declarations": "bash scripts/sync-declarations.sh",
28
- "test-suite": "bash scripts/run-tests.sh tests/test-suite.ts --lease 3",
29
- "test-smoke": "bash scripts/run-tests.sh tests/test-smoke.ts --lease 3",
30
- "test-upgrade": "bash scripts/run-tests.sh tests/test-upgrade.ts --lease 6",
28
+ "test-suite": "bash scripts/run-tests-local.sh tests/test-suite.ts",
29
+ "test-smoke": "bash scripts/run-tests-local.sh tests/test-smoke.ts",
30
+ "test-upgrade": "bash scripts/run-tests-local.sh tests/test-upgrade.ts",
31
31
  "redeploy": "bash scripts/redeploy.sh",
32
32
  "sandbox": "npx tsx sandbox/main.ts",
33
33
  "stamp-canister": "bash scripts/stamp-canister-commit.sh"
@@ -55,6 +55,6 @@
55
55
  "sultanaAssets": {
56
56
  "repo": "sultana-assets-rust",
57
57
  "repoUrl": "https://github.com/JSM-Sultana/sultana-assets-rust",
58
- "commit": "7a2c7e96a4253f0203d9694c9200189f61044c85"
58
+ "commit": "a5db6bd52dc751a7603c5c5fdc16fb73e0486882"
59
59
  }
60
60
  }