@jsm-mit/sultana-assets-rust-package 0.1.0 → 0.3.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
@@ -35,6 +35,7 @@ One class per part of the canister:
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
37
  | `AssetsRustAssetsActor` | `get_asset_info`, `list_assets`, `http_request`, `assetUrl` | anybody |
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 |
38
39
 
39
40
  Every method is `…AsyncUnsafe` and throws one of three errors, like the other wrapper
40
41
  packages:
@@ -56,9 +57,9 @@ types are exported too, and `./declarations/*` gives the generated files.
56
57
  `src/` is browser-safe — the same package serves the biz panel, the playground and the
57
58
  transcoding worker.
58
59
 
59
- Scripts: `build`, `typecheck`, `sync-declarations`, `test-suite`, `redeploy`, `sandbox`
60
+ Scripts: `build`, `typecheck`, `sync-declarations`, `test-suite`, `test-smoke`, `test-upgrade`, `redeploy`, `sandbox`
60
61
  (an interactive console: `npm run sandbox -- <canister-id>`, caller from `IDENTITY_PEM` in
61
- `.env` or anonymous), `stamp-canister`, `publish-public`. See `CLAUDE.md` for who runs what.
62
+ `.env.sandbox` or anonymous), `stamp-canister`, `publish-public`. See `CLAUDE.md` for who runs what.
62
63
 
63
64
  ## Integration tests
64
65
 
@@ -67,6 +68,11 @@ table's `short-living` slot (lease 3 minutes) and runs `tests/test-suite.ts` the
67
68
  `.env`, no PEMs: every identity is generated per run. A fresh canister has no core canister,
68
69
  so the suite claims that claim-once seat for a generated identity and plays core itself.
69
70
 
71
+ `npm run test-smoke` does the same with `tests/test-smoke.ts`: setup, then one write and one
72
+ read per domain (worker list, a few-byte single-shot upload read back over HTTP, a worker
73
+ result, a salon upload refused by the stand-in core), one replication wait, ≈ 30 s.
74
+ `publish-public` runs it too.
75
+
70
76
  What the suite covers: the core seat and the worker list; chunked and single-shot uploads
71
77
  with every owner and size check; reads, HTTP 200/206/404, certification header and
72
78
  streaming; the worker's system upload with a poster; `delete_asset` taking the thumb along;
@@ -79,8 +85,9 @@ the suite never touches it.
79
85
 
80
86
  Bump the version by hand, commit, push, then `npm run publish-public`. It refuses unless this
81
87
  repo and `../sultana-assets-rust` are on a clean, pushed `master` and the version is new on
82
- npm; it reinstalls the canister's master into `short-living` (lease 1), checks that its
88
+ npm; it reinstalls the canister's master into `short-living` (lease 2), checks that its
83
89
  `candid:service` equals `declarations/sultana-assets-rust/sultana-assets-rust.did` as dfx
84
- rewrites it (the hand-written .did is normalized by a local `dfx build --check` first), stamps
90
+ rewrites it (the hand-written .did is normalized by a local `dfx build --check` first), runs
91
+ the smoke test (`tests/test-smoke.ts`) on that same deploy, stamps
85
92
  `sultanaAssets.{repo, repoUrl, commit}` into package.json, publishes and commits `Version bump
86
93
  to <version>`.
@@ -75,11 +75,14 @@ type ReserveRenderArgs = record {
75
75
  service : () -> {
76
76
  abort_upload : (text) -> (bool);
77
77
  delete_asset : (text) -> (bool);
78
+ delete_my_avatar : () -> (bool);
78
79
  delete_salon_asset : (DeleteSalonAssetArgs) -> (bool);
79
80
  fail_system_upload : (FailSystemUploadArgs) -> (bool);
80
81
  finish_system_upload : (FinishSystemUploadArgs) -> (text);
81
82
  finish_upload : (text) -> (text);
82
83
  get_asset_info : (text) -> (opt AssetInfo) query;
84
+ get_avatar : (principal) -> (opt text) query;
85
+ get_coordinator : () -> (opt principal) query;
83
86
  get_core_canister : () -> (opt principal) query;
84
87
  get_workers : () -> (vec WorkerEntry) query;
85
88
  http_request : (HttpRequest) -> (HttpResponse) query;
@@ -88,6 +91,7 @@ service : () -> {
88
91
  reserve_render : (ReserveRenderArgs) -> (ReserveTranscodingUpload);
89
92
  reserve_transcoding_upload : (ReserveTranscodingUploadArgs) -> (ReserveTranscodingUpload);
90
93
  run_reservation_sweep : (nat64) -> (nat32);
94
+ set_coordinator : (opt principal) -> (bool);
91
95
  set_core_canister : (principal) -> (bool);
92
96
  set_workers : (vec WorkerEntry) -> (bool);
93
97
  start_upload : (StartUploadArgs) -> (text);
@@ -95,4 +99,5 @@ service : () -> {
95
99
  upload_asset : (UploadArgs) -> (text);
96
100
  upload_chunk : (text, nat32, blob) -> ();
97
101
  upload_image : (UploadImageArgs) -> (text);
102
+ upload_my_avatar : (blob) -> (text);
98
103
  }
@@ -95,11 +95,14 @@ export interface WorkerEntry { 'url' : string, 'principal' : Principal }
95
95
  export interface _SERVICE {
96
96
  'abort_upload' : ActorMethod<[string], boolean>,
97
97
  'delete_asset' : ActorMethod<[string], boolean>,
98
+ 'delete_my_avatar' : ActorMethod<[], boolean>,
98
99
  'delete_salon_asset' : ActorMethod<[DeleteSalonAssetArgs], boolean>,
99
100
  'fail_system_upload' : ActorMethod<[FailSystemUploadArgs], boolean>,
100
101
  'finish_system_upload' : ActorMethod<[FinishSystemUploadArgs], string>,
101
102
  'finish_upload' : ActorMethod<[string], string>,
102
103
  'get_asset_info' : ActorMethod<[string], [] | [AssetInfo]>,
104
+ 'get_avatar' : ActorMethod<[Principal], [] | [string]>,
105
+ 'get_coordinator' : ActorMethod<[], [] | [Principal]>,
103
106
  'get_core_canister' : ActorMethod<[], [] | [Principal]>,
104
107
  'get_workers' : ActorMethod<[], Array<WorkerEntry>>,
105
108
  'http_request' : ActorMethod<[HttpRequest], HttpResponse>,
@@ -114,6 +117,7 @@ export interface _SERVICE {
114
117
  ReserveTranscodingUpload
115
118
  >,
116
119
  'run_reservation_sweep' : ActorMethod<[bigint], number>,
120
+ 'set_coordinator' : ActorMethod<[[] | [Principal]], boolean>,
117
121
  'set_core_canister' : ActorMethod<[Principal], boolean>,
118
122
  'set_workers' : ActorMethod<[Array<WorkerEntry>], boolean>,
119
123
  'start_upload' : ActorMethod<[StartUploadArgs], string>,
@@ -124,6 +128,7 @@ export interface _SERVICE {
124
128
  undefined
125
129
  >,
126
130
  'upload_image' : ActorMethod<[UploadImageArgs], string>,
131
+ 'upload_my_avatar' : ActorMethod<[Uint8Array | number[]], string>,
127
132
  }
128
133
  export declare const idlFactory: IDL.InterfaceFactory;
129
134
  export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];
@@ -106,11 +106,14 @@ export const idlFactory = ({ IDL }) => {
106
106
  return IDL.Service({
107
107
  'abort_upload' : IDL.Func([IDL.Text], [IDL.Bool], []),
108
108
  'delete_asset' : IDL.Func([IDL.Text], [IDL.Bool], []),
109
+ 'delete_my_avatar' : IDL.Func([], [IDL.Bool], []),
109
110
  'delete_salon_asset' : IDL.Func([DeleteSalonAssetArgs], [IDL.Bool], []),
110
111
  'fail_system_upload' : IDL.Func([FailSystemUploadArgs], [IDL.Bool], []),
111
112
  'finish_system_upload' : IDL.Func([FinishSystemUploadArgs], [IDL.Text], []),
112
113
  'finish_upload' : IDL.Func([IDL.Text], [IDL.Text], []),
113
114
  'get_asset_info' : IDL.Func([IDL.Text], [IDL.Opt(AssetInfo)], ['query']),
115
+ 'get_avatar' : IDL.Func([IDL.Principal], [IDL.Opt(IDL.Text)], ['query']),
116
+ 'get_coordinator' : IDL.Func([], [IDL.Opt(IDL.Principal)], ['query']),
114
117
  'get_core_canister' : IDL.Func([], [IDL.Opt(IDL.Principal)], ['query']),
115
118
  'get_workers' : IDL.Func([], [IDL.Vec(WorkerEntry)], ['query']),
116
119
  'http_request' : IDL.Func([HttpRequest], [HttpResponse], ['query']),
@@ -131,6 +134,7 @@ export const idlFactory = ({ IDL }) => {
131
134
  [],
132
135
  ),
133
136
  'run_reservation_sweep' : IDL.Func([IDL.Nat64], [IDL.Nat32], []),
137
+ 'set_coordinator' : IDL.Func([IDL.Opt(IDL.Principal)], [IDL.Bool], []),
134
138
  'set_core_canister' : IDL.Func([IDL.Principal], [IDL.Bool], []),
135
139
  'set_workers' : IDL.Func([IDL.Vec(WorkerEntry)], [IDL.Bool], []),
136
140
  'start_upload' : IDL.Func([StartUploadArgs], [IDL.Text], []),
@@ -138,6 +142,7 @@ export const idlFactory = ({ IDL }) => {
138
142
  'upload_asset' : IDL.Func([UploadArgs], [IDL.Text], []),
139
143
  'upload_chunk' : IDL.Func([IDL.Text, IDL.Nat32, IDL.Vec(IDL.Nat8)], [], []),
140
144
  'upload_image' : IDL.Func([UploadImageArgs], [IDL.Text], []),
145
+ 'upload_my_avatar' : IDL.Func([IDL.Vec(IDL.Nat8)], [IDL.Text], []),
141
146
  });
142
147
  };
143
148
  export const init = ({ IDL }) => { return []; };
@@ -27,6 +27,27 @@ export declare class AssetsRustAdminActor extends ActorBase {
27
27
  * @throws CriticalCanisterError - Transport failure or an unreachable canister.
28
28
  */
29
29
  getCoreCanisterAsyncUnsafe(): Promise<string | null>;
30
+ /**
31
+ * Core-canister only: the coordinator of the sharded group (sultana-sharded) whose workers
32
+ * hold the salons — the trusted calls of a salon then go to its worker. The real path is
33
+ * the group core's `linkAssetsToGroup` (sultana-sharded-package
34
+ * `AdminActor.linkAssetsToGroupAsyncUnsafe`); `null` goes back to the one core.
35
+ * @param coordinatorPrincipal - The group's coordinator, or `null`.
36
+ * @returns A Promise that resolves to `true` when set, `false` when the caller is not the
37
+ * core canister.
38
+ * @throws CanisterError - The canister trapped.
39
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
40
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
41
+ */
42
+ setCoordinatorAsyncUnsafe(coordinatorPrincipal: string | null): Promise<boolean>;
43
+ /**
44
+ * @returns A Promise that resolves to the coordinator of the sharded group, or `null` when
45
+ * the canister serves one core.
46
+ * @throws CanisterError - The canister trapped.
47
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
48
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
49
+ */
50
+ getCoordinatorAsyncUnsafe(): Promise<string | null>;
30
51
  /**
31
52
  * Core-canister only: replaces the transcoding-worker list. The real path is core's
32
53
  * superadmin proxy (sultana-core-motoko-package `AdminActor.setAssetWorkersAsyncUnsafe`).
@@ -1 +1 @@
1
- {"version":3,"file":"admin-actor.d.ts","sourceRoot":"","sources":["../../src/actors/admin-actor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;IAC/C;;;;;;;;;OASG;IACU,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIhF;;;;;;OAMG;IACU,0BAA0B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKjE;;;;;;;;;OASG;IACU,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3E;;;;;;OAMG;IACU,qBAAqB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAK3D;;;;;;;;;;OAUG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE;;;;;;;;OAQG;IACU,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAG9E"}
1
+ {"version":3,"file":"admin-actor.d.ts","sourceRoot":"","sources":["../../src/actors/admin-actor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;IAC/C;;;;;;;;;OASG;IACU,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIhF;;;;;;OAMG;IACU,0BAA0B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKjE;;;;;;;;;;;OAWG;IACU,yBAAyB,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7F;;;;;;OAMG;IACU,yBAAyB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKhE;;;;;;;;;OASG;IACU,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3E;;;;;;OAMG;IACU,qBAAqB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAK3D;;;;;;;;;;OAUG;IACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE;;;;;;;;OAQG;IACU,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAG9E"}
@@ -33,6 +33,32 @@ export class AssetsRustAdminActor extends ActorBase {
33
33
  const result = await this.executeFunctionAsyncUnsafe(() => this.actor.get_core_canister());
34
34
  return result.length === 0 ? null : result[0].toText();
35
35
  }
36
+ /**
37
+ * Core-canister only: the coordinator of the sharded group (sultana-sharded) whose workers
38
+ * hold the salons — the trusted calls of a salon then go to its worker. The real path is
39
+ * the group core's `linkAssetsToGroup` (sultana-sharded-package
40
+ * `AdminActor.linkAssetsToGroupAsyncUnsafe`); `null` goes back to the one core.
41
+ * @param coordinatorPrincipal - The group's coordinator, or `null`.
42
+ * @returns A Promise that resolves to `true` when set, `false` when the caller is not the
43
+ * core canister.
44
+ * @throws CanisterError - The canister trapped.
45
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
46
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
47
+ */
48
+ async setCoordinatorAsyncUnsafe(coordinatorPrincipal) {
49
+ return this.executeFunctionAsyncUnsafe(() => this.actor.set_coordinator(coordinatorPrincipal === null ? [] : [Principal.fromText(coordinatorPrincipal)]));
50
+ }
51
+ /**
52
+ * @returns A Promise that resolves to the coordinator of the sharded group, or `null` when
53
+ * the canister serves one core.
54
+ * @throws CanisterError - The canister trapped.
55
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
56
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
57
+ */
58
+ async getCoordinatorAsyncUnsafe() {
59
+ const result = await this.executeFunctionAsyncUnsafe(() => this.actor.get_coordinator());
60
+ return result.length === 0 ? null : result[0].toText();
61
+ }
36
62
  /**
37
63
  * Core-canister only: replaces the transcoding-worker list. The real path is core's
38
64
  * superadmin proxy (sultana-core-motoko-package `AdminActor.setAssetWorkersAsyncUnsafe`).
@@ -0,0 +1,54 @@
1
+ import { ActorBase } from "../actor-base.js";
2
+ import type { HttpGetInput, HttpResponseView } from "../interfaces.js";
3
+ /**
4
+ * The caller's own avatar. Every signed-in principal has at most one and is the only one who
5
+ * can change it — there is no principal argument on the writes. Only a principal with a
6
+ * profile in sultana-core-motoko may upload one (core's isUser); there is no salon and no quota. The canister refuses a file over
7
+ * `ASSETS_RUST_LIMITS.avatarMaxBytes` or over `avatarMaxSide` px and stores it re-encoded as
8
+ * JPEG (EXIF/GPS dropped), so scale it down on the client first.
9
+ */
10
+ export declare class AssetsRustAvatarActor extends ActorBase {
11
+ /**
12
+ * Stores a new avatar for the caller and deletes the previous one.
13
+ * @param data - The image (JPEG, PNG or WebP).
14
+ * @returns A Promise that resolves to the new avatar's asset id.
15
+ * @throws CanisterError - The canister trapped: anonymous caller, "avatar too large: …",
16
+ * "avatar refused: …" (no profile in core, or core unreachable), "avatar rejected:
17
+ * …" (not an image, or a side over the limit).
18
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
19
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
20
+ */
21
+ uploadMyAvatarAsyncUnsafe(data: Uint8Array): Promise<string>;
22
+ /**
23
+ * Deletes the caller's avatar.
24
+ * @returns A Promise that resolves to `true` when an avatar was removed, `false` when the
25
+ * caller had none.
26
+ * @throws CanisterError - The canister trapped: anonymous caller.
27
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
28
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
29
+ */
30
+ deleteMyAvatarAsyncUnsafe(): Promise<boolean>;
31
+ /**
32
+ * @param owner - The principal (text).
33
+ * @returns A Promise that resolves to the asset id of the principal's current avatar, or
34
+ * `null` when they have none.
35
+ * @throws CanisterError - The canister trapped.
36
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
37
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
38
+ */
39
+ getAvatarAsyncUnsafe(owner: string): Promise<string | null>;
40
+ /**
41
+ * GET of `/avatar/<owner>` through the canister's `http_request` query — what the HTTP
42
+ * gateway calls: 200 with the current avatar (5-minute cache), 404 when there is none.
43
+ * @param owner - The principal (text).
44
+ * @param input - Request headers.
45
+ * @returns A Promise that resolves to the response.
46
+ * @throws CanisterError - The canister trapped.
47
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
48
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
49
+ */
50
+ httpGetAvatarAsyncUnsafe(owner: string, input?: HttpGetInput): Promise<HttpResponseView>;
51
+ /** The stable public URL of a principal's avatar on this canister (see {@link avatarUrl}). */
52
+ avatarUrl(owner: string): string;
53
+ }
54
+ //# sourceMappingURL=avatar-actor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatar-actor.d.ts","sourceRoot":"","sources":["../../src/actors/avatar-actor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGvE;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;IAChD;;;;;;;;;OASG;IACU,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAIzE;;;;;;;OAOG;IACU,yBAAyB,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1D;;;;;;;OAOG;IACU,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAKxE;;;;;;;;;OASG;IACU,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,YAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAYzG,8FAA8F;IACvF,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAG1C"}
@@ -0,0 +1,72 @@
1
+ import { Principal } from "@icp-sdk/core/principal";
2
+ import { ActorBase } from "../actor-base.js";
3
+ import { avatarUrl } from "../constants.js";
4
+ import { mapHttpResponseView } from "../mappers.js";
5
+ /**
6
+ * The caller's own avatar. Every signed-in principal has at most one and is the only one who
7
+ * can change it — there is no principal argument on the writes. Only a principal with a
8
+ * profile in sultana-core-motoko may upload one (core's isUser); there is no salon and no quota. The canister refuses a file over
9
+ * `ASSETS_RUST_LIMITS.avatarMaxBytes` or over `avatarMaxSide` px and stores it re-encoded as
10
+ * JPEG (EXIF/GPS dropped), so scale it down on the client first.
11
+ */
12
+ export class AssetsRustAvatarActor extends ActorBase {
13
+ /**
14
+ * Stores a new avatar for the caller and deletes the previous one.
15
+ * @param data - The image (JPEG, PNG or WebP).
16
+ * @returns A Promise that resolves to the new avatar's asset id.
17
+ * @throws CanisterError - The canister trapped: anonymous caller, "avatar too large: …",
18
+ * "avatar refused: …" (no profile in core, or core unreachable), "avatar rejected:
19
+ * …" (not an image, or a side over the limit).
20
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
21
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
22
+ */
23
+ async uploadMyAvatarAsyncUnsafe(data) {
24
+ return this.executeFunctionAsyncUnsafe(() => this.actor.upload_my_avatar(data));
25
+ }
26
+ /**
27
+ * Deletes the caller's avatar.
28
+ * @returns A Promise that resolves to `true` when an avatar was removed, `false` when the
29
+ * caller had none.
30
+ * @throws CanisterError - The canister trapped: anonymous caller.
31
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
32
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
33
+ */
34
+ async deleteMyAvatarAsyncUnsafe() {
35
+ return this.executeFunctionAsyncUnsafe(() => this.actor.delete_my_avatar());
36
+ }
37
+ /**
38
+ * @param owner - The principal (text).
39
+ * @returns A Promise that resolves to the asset id of the principal's current avatar, or
40
+ * `null` when they have none.
41
+ * @throws CanisterError - The canister trapped.
42
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
43
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
44
+ */
45
+ async getAvatarAsyncUnsafe(owner) {
46
+ const result = await this.executeFunctionAsyncUnsafe(() => this.actor.get_avatar(Principal.fromText(owner)));
47
+ return result.length === 0 ? null : result[0];
48
+ }
49
+ /**
50
+ * GET of `/avatar/<owner>` through the canister's `http_request` query — what the HTTP
51
+ * gateway calls: 200 with the current avatar (5-minute cache), 404 when there is none.
52
+ * @param owner - The principal (text).
53
+ * @param input - Request headers.
54
+ * @returns A Promise that resolves to the response.
55
+ * @throws CanisterError - The canister trapped.
56
+ * @throws CallRefusedAtInspectionStage - The call was refused at the inspect stage.
57
+ * @throws CriticalCanisterError - Transport failure or an unreachable canister.
58
+ */
59
+ async httpGetAvatarAsyncUnsafe(owner, input = {}) {
60
+ const response = await this.executeFunctionAsyncUnsafe(() => this.actor.http_request({
61
+ method: "GET",
62
+ url: `/avatar/${owner}`,
63
+ headers: Object.entries(input.headers ?? {}),
64
+ body: new Uint8Array(),
65
+ }));
66
+ return mapHttpResponseView(response);
67
+ }
68
+ /** The stable public URL of a principal's avatar on this canister (see {@link avatarUrl}). */
69
+ avatarUrl(owner) {
70
+ return avatarUrl(this.canisterId, owner);
71
+ }
72
+ }
@@ -21,12 +21,22 @@ export declare const ASSETS_RUST_LIMITS: {
21
21
  readonly maxMusicStartSec: 600;
22
22
  readonly minSlideDurationSec: 5;
23
23
  readonly maxSlideDurationSec: 60;
24
+ /** `upload_my_avatar`: the largest file, the largest side in px. Scale the image down
25
+ * on the client first (256 px JPEG fits well). */
26
+ readonly avatarMaxBytes: number;
27
+ readonly avatarMaxSide: 512;
24
28
  };
25
29
  /**
26
30
  * The public URL of a stored asset. Served from the raw host (`<id>.raw.icp0.io`), which
27
31
  * also answers Range requests (206, uncertified) that a `<video>` needs to seek.
28
32
  */
29
33
  export declare function assetUrl(canisterId: string, assetId: string): string;
34
+ /**
35
+ * The stable public URL of a principal's avatar: always their current one, 404 when they have
36
+ * none. Cached for 5 minutes, so a new avatar shows up for others within that time; add a
37
+ * query such as `?v=<asset id>` to see a fresh one at once.
38
+ */
39
+ export declare function avatarUrl(canisterId: string, owner: string): string;
30
40
  /** Splits `bytes` into chunks the canister accepts. An empty input is one empty chunk —
31
41
  * a session needs at least one. */
32
42
  export declare function splitIntoChunks(bytes: Uint8Array, chunkSizeBytes?: number): Uint8Array[];
@@ -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;;;;;CAKvF,CAAC;AAEX;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;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,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
@@ -21,6 +21,10 @@ export const ASSETS_RUST_LIMITS = {
21
21
  maxMusicStartSec: 600,
22
22
  minSlideDurationSec: 5,
23
23
  maxSlideDurationSec: 60,
24
+ /** `upload_my_avatar`: the largest file, the largest side in px. Scale the image down
25
+ * on the client first (256 px JPEG fits well). */
26
+ avatarMaxBytes: 64 * 1024,
27
+ avatarMaxSide: 512,
24
28
  };
25
29
  /**
26
30
  * The public URL of a stored asset. Served from the raw host (`<id>.raw.icp0.io`), which
@@ -29,6 +33,14 @@ export const ASSETS_RUST_LIMITS = {
29
33
  export function assetUrl(canisterId, assetId) {
30
34
  return `https://${canisterId}.raw.icp0.io/asset/${assetId}`;
31
35
  }
36
+ /**
37
+ * The stable public URL of a principal's avatar: always their current one, 404 when they have
38
+ * none. Cached for 5 minutes, so a new avatar shows up for others within that time; add a
39
+ * query such as `?v=<asset id>` to see a fresh one at once.
40
+ */
41
+ export function avatarUrl(canisterId, owner) {
42
+ return `https://${canisterId}.raw.icp0.io/avatar/${owner}`;
43
+ }
32
44
  /** Splits `bytes` into chunks the canister accepts. An empty input is one empty chunk —
33
45
  * a session needs at least one. */
34
46
  export function splitIntoChunks(bytes, chunkSizeBytes = ASSETS_RUST_LIMITS.chunkSizeBytes) {
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ export { AssetsRustUploadsActor } from "./actors/uploads-actor.js";
4
4
  export { AssetsRustSalonActor } from "./actors/salon-actor.js";
5
5
  export { AssetsRustWorkerActor } from "./actors/worker-actor.js";
6
6
  export { AssetsRustAssetsActor } from "./actors/assets-actor.js";
7
- export { ASSETS_RUST_LIMITS, DEFAULT_HOST, assetUrl, splitIntoChunks } from "./constants.js";
7
+ export { AssetsRustAvatarActor } from "./actors/avatar-actor.js";
8
+ export { ASSETS_RUST_LIMITS, DEFAULT_HOST, assetUrl, avatarUrl, splitIntoChunks } from "./constants.js";
8
9
  export type { AssetInfoView, WorkerView, ReservationView, TranscodingKindView, RenderModeView, StartUploadInput, UploadAssetInput, UploadImageInput, ReserveTranscodingUploadInput, ReserveRenderInput, SystemStartUploadInput, HttpGetInput, HttpResponseView, UploadProgress, ChunkedUploadOptions, } from "./interfaces.js";
9
10
  export { mapAssetInfoView, mapWorkerView, mapReservationView, mapHttpResponseView, toCandidTranscodingKind, toCandidRenderMode, toCandidOpt, } from "./mappers.js";
10
11
  export type { AssetInfo, WorkerEntry, ReserveTranscodingUpload, TranscodingKind, RenderMode, HttpRequest, HttpResponse, StreamingCallbackToken, StreamingCallbackHttpResponse, StreamingStrategy, } from "../declarations/sultana-assets-rust/sultana-assets-rust.did.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7F,YAAY,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,6BAA6B,EAC7B,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACH,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EACR,SAAS,EACT,WAAW,EACX,wBAAwB,EACxB,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,6BAA6B,EAC7B,iBAAiB,GACpB,MAAM,gEAAgE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxG,YAAY,EACR,aAAa,EACb,UAAU,EACV,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,6BAA6B,EAC7B,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACH,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EACR,SAAS,EACT,WAAW,EACX,wBAAwB,EACxB,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,6BAA6B,EAC7B,iBAAiB,GACpB,MAAM,gEAAgE,CAAC"}
package/dist/index.js CHANGED
@@ -4,5 +4,6 @@ export { AssetsRustUploadsActor } from "./actors/uploads-actor.js";
4
4
  export { AssetsRustSalonActor } from "./actors/salon-actor.js";
5
5
  export { AssetsRustWorkerActor } from "./actors/worker-actor.js";
6
6
  export { AssetsRustAssetsActor } from "./actors/assets-actor.js";
7
- export { ASSETS_RUST_LIMITS, DEFAULT_HOST, assetUrl, splitIntoChunks } from "./constants.js";
7
+ export { AssetsRustAvatarActor } from "./actors/avatar-actor.js";
8
+ export { ASSETS_RUST_LIMITS, DEFAULT_HOST, assetUrl, avatarUrl, splitIntoChunks } from "./constants.js";
8
9
  export { mapAssetInfoView, mapWorkerView, mapReservationView, mapHttpResponseView, toCandidTranscodingKind, toCandidRenderMode, toCandidOpt, } from "./mappers.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsm-mit/sultana-assets-rust-package",
3
- "version": "0.1.0",
3
+ "version": "0.3.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": "",
@@ -26,6 +26,8 @@
26
26
  "typecheck": "tsc --noEmit -p tsconfig.tests.json",
27
27
  "sync-declarations": "bash scripts/sync-declarations.sh",
28
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",
29
31
  "redeploy": "bash scripts/redeploy.sh",
30
32
  "sandbox": "npx tsx sandbox/main.ts",
31
33
  "stamp-canister": "bash scripts/stamp-canister-commit.sh"
@@ -53,6 +55,6 @@
53
55
  "sultanaAssets": {
54
56
  "repo": "sultana-assets-rust",
55
57
  "repoUrl": "https://github.com/JSM-Sultana/sultana-assets-rust",
56
- "commit": "a2fcfea02282aa858d683bddb26297f2091c431c"
58
+ "commit": "7a2c7e96a4253f0203d9694c9200189f61044c85"
57
59
  }
58
60
  }