@assembly-js/app-foundation 0.6.6 → 0.7.1

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/CHANGELOG.md CHANGED
@@ -19,6 +19,13 @@ A patch release never changes what an app's build requires; a minor release may
19
19
  not by hand; see `changes/README.md`. CI fails a PR that changes a check script without a
20
20
  `bump: minor` change file.
21
21
 
22
+ ## 0.7.1
23
+ - [fix] The location reporter no longer sends the host's embed-session params (`token`, `clientId`, `companyId`) with the app's location, so they never reach the host URL or a copied deep link.
24
+
25
+ ## 0.7.0
26
+ - [new] `deleteFile(key)` in `@assembly-js/app-foundation/server` permanently deletes a file stored with app-storage.
27
+ - [guidance] The assembly-storage skill documents `deleteFile`: deletion is permanent, so apps that want recovery hide the record first and delete the file later.
28
+
22
29
  ## 0.6.6
23
30
  - [guidance] The build agent ends a chat turn by describing what changed instead of reporting its type-check, build, or the screens it didn't open, and knows the platform builds the app after every turn. It still claims "fixed" or "working" only for what it exercised, and talks about verification when asked or when fixing a reported bug.
24
31
 
@@ -44,7 +44,7 @@ export const NON_CLIENT_EXPORTS = new Set([
44
44
  'createAssemblyReexchangeHandler', 'cronRoute',
45
45
  'decodeBearer', 'detectPdfFields', 'expandClientsByCompany', 'extractPdfText', 'fetchSessionFlags',
46
46
  'findResource', 'formatAirtableRecords', 'generateAppClient', 'getAirtableTableSchema', 'getCapturedRecords',
47
- 'getClientAccessScope', 'getCookielessBearer', 'getDownloadUrl', 'getInstalledAppIds', 'getIntegration',
47
+ 'getClientAccessScope', 'getCookielessBearer', 'getDownloadUrl', 'deleteFile', 'getInstalledAppIds', 'getIntegration',
48
48
  'getInternalUserAccess', 'getLabels', 'getSessionIdentity', 'getUploadUrl',
49
49
  'getVerifiedIdentity', 'gmailSendEmail', 'handlePaintedBeacon', 'handleRouteError',
50
50
  'hasClientFlag', 'hasFlag', 'installFetchCapture', 'installNetworkCapture',
@@ -5,3 +5,4 @@ export declare function getUploadUrl(fileName: string): Promise<{
5
5
  export declare function getDownloadUrl(key: string): Promise<{
6
6
  downloadUrl: string;
7
7
  }>;
8
+ export declare function deleteFile(key: string): Promise<void>;
@@ -40,3 +40,15 @@ export async function getDownloadUrl(key) {
40
40
  throw new Error('Download URL request failed: ' + res.status);
41
41
  return res.json();
42
42
  }
43
+ // NOTE: takes ONE argument. Deletion is permanent — the platform keeps no copy
44
+ // to restore, so apps that want recovery must hide the record first.
45
+ export async function deleteFile(key) {
46
+ if (JWT_SHAPE.test(key)) {
47
+ throw new Error('deleteFile: first argument looks like a session token. ' +
48
+ 'The token argument was removed — call deleteFile(key) with the storage key only.');
49
+ }
50
+ const assembly = await getInstallerAssemblyForCaller();
51
+ const res = await assembly.fetch('/v1/app-storage/object?key=' + encodeURIComponent(key), { method: 'DELETE' });
52
+ if (!res.ok)
53
+ throw new Error('Delete request failed: ' + res.status);
54
+ }
@@ -1,3 +1,9 @@
1
+ export declare const HOST_SESSION_PARAMS: string[];
2
+ /**
3
+ * The app's own query string with the host's embed-session params removed,
4
+ * as a `?`-prefixed string, or '' when nothing is left.
5
+ */
6
+ export declare function stripHostSessionParams(searchParams: URLSearchParams): string;
1
7
  export declare function getSurfacePrefixes(): string[];
2
8
  export declare const SURFACE_PREFIXES: string[];
3
9
  /**
@@ -1,4 +1,19 @@
1
1
  const DEFAULT_PREFIXES = ['/c', '/n'];
2
+ // The host puts these on the iframe src to open the embed session. They are
3
+ // not part of the app's location, and reporting them would put the token in
4
+ // the host URL, browser history and every copied deep link.
5
+ export const HOST_SESSION_PARAMS = ['token', 'clientId', 'companyId'];
6
+ /**
7
+ * The app's own query string with the host's embed-session params removed,
8
+ * as a `?`-prefixed string, or '' when nothing is left.
9
+ */
10
+ export function stripHostSessionParams(searchParams) {
11
+ const own = new URLSearchParams(searchParams);
12
+ for (const key of HOST_SESSION_PARAMS)
13
+ own.delete(key);
14
+ const qs = own.toString();
15
+ return qs ? `?${qs}` : '';
16
+ }
2
17
  // Read surface prefixes from the build-time env value injected by withAssemblyDev.
3
18
  // Falls back to the historical /c and /n defaults so apps on an older image
4
19
  // continue to work without a rebuild.
@@ -2,7 +2,7 @@
2
2
  import { Suspense, createElement, useEffect, useRef } from 'react';
3
3
  import { usePathname, useSearchParams, useRouter } from 'next/navigation';
4
4
  import { AssemblyBridge } from '@assembly-js/app-bridge';
5
- import { getSurfacePrefix, stripPrefix, addPrefix } from './location-prefix.js';
5
+ import { getSurfacePrefix, stripPrefix, addPrefix, stripHostSessionParams } from './location-prefix.js';
6
6
  function LocationReporterInner() {
7
7
  const pathname = usePathname();
8
8
  const searchParams = useSearchParams();
@@ -19,8 +19,7 @@ function LocationReporterInner() {
19
19
  if (!bridgeLocation)
20
20
  return;
21
21
  const path = stripPrefix(pathname, prefix);
22
- const qs = searchParams.toString();
23
- bridgeLocation.report(path, qs ? `?${qs}` : '');
22
+ bridgeLocation.report(path, stripHostSessionParams(searchParams));
24
23
  }, [bridgeLocation, pathname, searchParams, prefix]);
25
24
  // Register the navigate handler once; rely on prefixRef for the current prefix.
26
25
  useEffect(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.6.6",
3
+ "version": "0.7.1",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,10 +11,11 @@ Use app-storage for files that belong to YOUR app. For files the user expects to
11
11
 
12
12
  ## Server helpers — `@assembly-js/app-foundation/server`
13
13
 
14
- **Both helpers ship in `@assembly-js/app-foundation/server`, an installed dependency** — import them; do NOT recreate or rewrite them:
14
+ **All three helpers ship in `@assembly-js/app-foundation/server`, an installed dependency** — import them; do NOT recreate or rewrite them:
15
15
 
16
16
  - `getUploadUrl(fileName)` → `{ key, uploadUrl }` — POSTs `{ fileName }` to the platform. Takes **one argument** (the file name only). If you were calling it as `getUploadUrl(token, fileName)` (an old two-arg shape), remove the token — auth is in the bearer cookie and the function will throw if a token is passed as the file name.
17
17
  - `getDownloadUrl(key)` → `{ downloadUrl }` — mints a fresh pre-signed GET. Takes **one argument** (the storage key only). Same note: remove any leading token argument.
18
+ - `deleteFile(key)` → `void` — permanently deletes the stored object. Takes **one argument** (the storage key only); the function throws if a token is passed as the key. Deletion is irreversible, so keep the keys in your own database to know what to delete.
18
19
 
19
20
  Store the `key` it returns; never construct an S3 key yourself, and never parse the original file name back out of the key — the platform normalizes the name when forming the key, so the stored `key` is not guaranteed to contain your exact `fileName`. Keep the display name as your own metadata (see "Storing and downloading"). `fileName` must be a bare file name (`invoice.pdf`), never a path — the platform rejects any fileName containing `/` or `..`. Don't prefix folders or namespaces onto it; the platform already scopes the key to the workspace and makes it unique. If you need grouping, store that in your own database alongside the key.
20
21
 
@@ -123,6 +124,30 @@ await downloadViaBlob(`/api/items/${id}/download`, 'report.pdf');
123
124
 
124
125
  Do NOT use `<a href>` links to the download route — plain links can't send the CSRF header `authFetch` adds, so the route rejects them with 403. Always use a button with an `onClick` that calls `authFetch` or `downloadViaBlob`.
125
126
 
127
+ ## Deleting a file
128
+
129
+ `deleteFile(key)` removes the object from storage. Deletion is **permanent and irreversible** — there is no trash, no undo, and no version to restore. If your app needs recovery (an "undo", a trash view, soft-delete), hide the record in your own database first and call `deleteFile` only later, once the user can no longer bring it back.
130
+
131
+ **Route handler** (`app/api/app-storage/delete/route.ts`):
132
+
133
+ ```typescript
134
+ import { NextRequest, NextResponse } from 'next/server';
135
+ import { handleRouteError, deleteFile } from '@assembly-js/app-foundation/server';
136
+
137
+ export async function DELETE(request: NextRequest) {
138
+ try {
139
+ const id = request.nextUrl.searchParams.get('id') ?? '';
140
+ const key = await getStoredFileKey(id); // your own database lookup by record id
141
+ await deleteFile(key);
142
+ return new NextResponse(null, { status: 204 });
143
+ } catch (err) {
144
+ return handleRouteError(err, 'Delete request failed');
145
+ }
146
+ }
147
+ ```
148
+
149
+ Only delete keys you stored yourself (look the key up from your own database by record id rather than trusting a key sent by the client), and remove or hide your own record at the same time.
150
+
126
151
  ## Server needs the bytes (parse / process an uploaded file)
127
152
 
128
153
  When the server must read the file too — extract PDF text, parse a CSV, generate a thumbnail — the
@@ -189,5 +214,6 @@ switch (detection.method) {
189
214
  - Do NOT embed a pre-signed download URL in rendered HTML — it expires; mint a fresh one per click.
190
215
  - Do NOT construct the S3 key yourself — store the key the platform returns.
191
216
  - Do NOT put `/` in `fileName` (e.g. `uploads/${file.name}`) — the API rejects it; send the bare file name.
217
+ - Do NOT call `deleteFile` expecting to recover the file later — deletion is permanent; hide the record first and delete the file after the recovery window.
192
218
  - Do NOT use app-storage for files the user wants in the portal Files tab — use `createFile`.
193
219
  - Do NOT pass any auth token as a URL query parameter for downloads — use `authFetch`, which adds the CSRF header while the bearer rides the cookie.