From 8502125d1bfe860279df1bb8c61d6be2771d5153 Mon Sep 17 00:00:00 2001 From: priosshrsth Date: Tue, 18 Aug 2026 09:09:19 +0000 Subject: [PATCH 1/2] chore: bump version to 0.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 639baa4..de2bfd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assembly-kit", - "version": "0.0.4", + "version": "0.0.5", "description": "TypeScript SDK for the Assembly platform", "homepage": "https://github.com/madebyoutside/assembly-kit#readme", "bugs": { From fe4ba0f14bb963a6c111b9cee3cbc1659b64826b Mon Sep 17 00:00:00 2001 From: priosshrsth Date: Tue, 18 Aug 2026 11:53:53 +0000 Subject: [PATCH 2/2] feat(token): add isProxying for internal users previewing as a client Assembly tokens can carry both internalUserId and clientId when an internal user proxies as a client from the CRM. Both isClientUser and isInternalUser return true in that case, so a plain if/else on either one silently picks a branch based on ordering. isProxying makes the case nameable. Also corrects the README claim that isClientUser requires companyId. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 1 + README.md | 3 ++- src/token/assembly-token.ts | 9 +++++++++ tests/fixtures/tokens.ts | 10 ++++++++++ tests/token.test.ts | 17 +++++++++++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index fcadd08..2f47a67 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -126,6 +126,7 @@ token.companyId; // string | undefined token.internalUserId; // string | undefined token.isClientUser; // boolean token.isInternalUser; // boolean +token.isProxying; // boolean — internal user previewing as a client (both IDs present) const client = token.ensureIsClient(); // ClientTokenPayload (throws if not client) const internal = token.ensureIsInternalUser(); // InternalUserTokenPayload (throws if not internal) diff --git a/README.md b/README.md index c26411c..cc4804b 100644 --- a/README.md +++ b/README.md @@ -358,8 +358,9 @@ token.tokenId; // string | undefined — present in some marketplace tokens token.baseUrl; // string | undefined — overrides the API base URL if set // Identity checks -token.isClientUser; // true if clientId + companyId are present +token.isClientUser; // true if clientId is present token.isInternalUser; // true if internalUserId is present +token.isProxying; // true if BOTH are present — internal user previewing as a client via CRM // Throwing guards — return narrowed payload type or throw AssemblyUnauthorizedError const clientPayload = token.ensureIsClient(); // ClientTokenPayload diff --git a/src/token/assembly-token.ts b/src/token/assembly-token.ts index 8e9942c..92d6611 100644 --- a/src/token/assembly-token.ts +++ b/src/token/assembly-token.ts @@ -117,6 +117,15 @@ export class AssemblyToken { return this.payload.internalUserId !== undefined; } + /** + * `true` when an internal user is proxying as a client (CRM "preview as client"): + * both `internalUserId` and `clientId` are present. Both `isClientUser` and + * `isInternalUser` are also `true` in this case — branch on this getter first. + */ + get isProxying(): boolean { + return this.payload.internalUserId !== undefined && this.payload.clientId !== undefined; + } + /** * Assert that the token belongs to a client user and return the narrowed payload. * @throws {AssemblyUnauthorizedError} If the token does not represent a client user. diff --git a/tests/fixtures/tokens.ts b/tests/fixtures/tokens.ts index b2fd1f4..2a33155 100644 --- a/tests/fixtures/tokens.ts +++ b/tests/fixtures/tokens.ts @@ -103,3 +103,13 @@ if (paddedWsId === undefined) { throw new Error("workspaceId missing in padded payload"); } export const BLOCK_ALIGNED_WORKSPACE_ID: string = paddedWsId; + +// --------------------------------------------------------------------------- +// Proxy token: internal user acting as a client via CRM +// --------------------------------------------------------------------------- +export const PROXY_TOKEN: string = encryptPayload(TEST_API_KEY, { + clientId: TEST_CLIENT_ID, + companyId: TEST_COMPANY_ID, + internalUserId: TEST_INTERNAL_USER_ID, + workspaceId: TEST_WORKSPACE_ID, +}); diff --git a/tests/token.test.ts b/tests/token.test.ts index f763b4b..f7bf916 100644 --- a/tests/token.test.ts +++ b/tests/token.test.ts @@ -13,6 +13,7 @@ import { BLOCK_ALIGNED_WORKSPACE_ID, CLIENT_TOKEN, INTERNAL_USER_TOKEN, + PROXY_TOKEN, TEST_API_KEY, TEST_BASE_URL, TEST_CLIENT_ID, @@ -192,6 +193,22 @@ describe("AssemblyToken getters", () => { // Identity checks // --------------------------------------------------------------------------- describe("AssemblyToken identity checks", () => { + it("proxy token (internalUserId + clientId) is flagged as proxying", () => { + const t = new AssemblyToken({ apiKey: TEST_API_KEY, token: PROXY_TOKEN }); + expect(t.isProxying).toBe(true); + expect(t.isClientUser).toBe(true); + expect(t.isInternalUser).toBe(true); + expect(t.ensureIsClient().clientId).toBe(TEST_CLIENT_ID); + expect(t.ensureIsInternalUser().internalUserId).toBe(TEST_INTERNAL_USER_ID); + }); + + it("isProxying is false for single-identity tokens", () => { + expect(new AssemblyToken({ apiKey: TEST_API_KEY, token: CLIENT_TOKEN }).isProxying).toBe(false); + expect(new AssemblyToken({ apiKey: TEST_API_KEY, token: INTERNAL_USER_TOKEN }).isProxying).toBe( + false, + ); + }); + it("isClientUser is true for client tokens", () => { const t = new AssemblyToken({ apiKey: TEST_API_KEY, token: CLIENT_TOKEN }); expect(t.isClientUser).toBe(true);