diff --git a/connect/README.md b/connect/README.md index 8d29ae584..b95a85864 100644 --- a/connect/README.md +++ b/connect/README.md @@ -123,6 +123,8 @@ const messageId = await message({ > Optional: Pass `returnAssignmentSlot: true` to `message` to get the scheduled slot of this message +> Optional: Pass `returnMessageId: true` with `returnAssignmentSlot: true` to return both values as `{ slot, id }` + > You can pass a 32 byte `anchor` to `message` which will be set on the DataItem #### `signMessage` diff --git a/connect/package.json b/connect/package.json index 25a6cb982..0818028d3 100644 --- a/connect/package.json +++ b/connect/package.json @@ -1,6 +1,6 @@ { "name": "@permaweb/aoconnect", - "version": "0.0.94", + "version": "0.0.95", "repository": { "type": "git", "url": "https://github.com/permaweb/ao.git", diff --git a/connect/src/client/ao-core.js b/connect/src/client/ao-core.js index ebf4aa102..0dc8116ee 100644 --- a/connect/src/client/ao-core.js +++ b/connect/src/client/ao-core.js @@ -44,6 +44,26 @@ const getTags = (args) => const getData = (args) => args.data ?? '1984' +const getMessageId = (response, parsedResponse) => + parsedResponse.id ?? + parsedResponse.messageId ?? + parsedResponse.message?.id ?? + response.headers?.get?.('id') ?? + response.headers?.get?.('message') + +const getMessageResponse = ({ response, parsedResponse, returnAssignmentSlot, returnMessageId }) => { + const slot = parsedResponse.slot + + if (returnMessageId) { + const id = getMessageId(response, parsedResponse) + if (!id) throw new Error('Message id not found in response') + if (returnAssignmentSlot) return { slot, id } + return id + } + + return slot +} + export function requestWith(deps) { return async (args) => { try { @@ -126,7 +146,14 @@ export function messageWith(deps) { const parsedResponse = await response.json() if (args.opts?.fullResponse) return normalizeOutput(parsedResponse) - else return parsedResponse.slot + else { + return getMessageResponse({ + response, + parsedResponse, + returnAssignmentSlot: args.returnAssignmentSlot, + returnMessageId: args.returnMessageId + }) + } } else throw new Error('Error sending message') } catch (e) { diff --git a/connect/src/client/ao-core.unit.test.js b/connect/src/client/ao-core.unit.test.js new file mode 100644 index 000000000..a21fa530f --- /dev/null +++ b/connect/src/client/ao-core.unit.test.js @@ -0,0 +1,58 @@ +import { describe, test } from 'node:test' +import * as assert from 'node:assert' + +import { messageWith } from './ao-core.js' + +function responseWith (body, headers = {}) { + return { + ok: true, + headers: { + get: (name) => headers[name] ?? headers[name.toLowerCase()] ?? null + }, + json: async () => body + } +} + +function createMessage (body, headers) { + return messageWith({ + aoCore: { + request: async () => responseWith(body, headers) + } + }) +} + +describe('ao-core message', () => { + test('returns the assignment slot by default', async () => { + const message = createMessage({ slot: 42, id: 'message-123' }) + + const res = await message({ process: 'process-asdf' }) + + assert.equal(res, 42) + }) + + test('returns the message id when returnMessageId is true', async () => { + const message = createMessage({ slot: 42, id: 'message-123' }) + + const res = await message({ + process: 'process-asdf', + returnMessageId: true + }) + + assert.equal(res, 'message-123') + }) + + test('returns slot and id when returnAssignmentSlot and returnMessageId are true', async () => { + const message = createMessage({ slot: 42, id: 'message-123' }) + + const res = await message({ + process: 'process-asdf', + returnAssignmentSlot: true, + returnMessageId: true + }) + + assert.deepStrictEqual(res, { + slot: 42, + id: 'message-123' + }) + }) +}) diff --git a/connect/src/dal.js b/connect/src/dal.js index f15e46f29..2ec3371fc 100644 --- a/connect/src/dal.js +++ b/connect/src/dal.js @@ -54,7 +54,8 @@ export const deployMessageSchema = z.function() tags: z.array(tagSchema), anchor: z.string().optional(), signer: z.any().nullish(), - returnAssignmentSlot: z.boolean().optional() + returnAssignmentSlot: z.boolean().optional(), + returnMessageId: z.boolean().optional() })) .returns(z.promise( z.object({ diff --git a/connect/src/lib/message/index.js b/connect/src/lib/message/index.js index 433777f27..1353a1a6f 100644 --- a/connect/src/lib/message/index.js +++ b/connect/src/lib/message/index.js @@ -15,10 +15,12 @@ import { prepareMessageWith, sendSignedMessageWith, uploadMessageWith } from './ * @property {{ name: string, value: string }[]} [tags] * @property {string} [anchor] * @property {Types['signer']} [signer] + * @property {boolean} [returnAssignmentSlot] + * @property {boolean} [returnMessageId] * * @callback SendMessage * @param {SendMessageArgs} args - * @returns {Promise} the id of the data item that represents this message + * @returns {Promise} the id of the data item that represents this message, the assignment slot, or both * * @param {Env1} - the environment * @returns {SendMessage} @@ -26,10 +28,20 @@ import { prepareMessageWith, sendSignedMessageWith, uploadMessageWith } from './ export function messageWith (env) { const uploadMessage = uploadMessageWith(env) - return ({ process, data, tags, anchor, signer, returnAssignmentSlot }) => { - return of({ id: process, data, tags, anchor, signer, returnAssignmentSlot }) + return ({ process, data, tags, anchor, signer, returnAssignmentSlot, returnMessageId }) => { + return of({ id: process, data, tags, anchor, signer, returnAssignmentSlot, returnMessageId }) .chain(uploadMessage) - .map((ctx) => returnAssignmentSlot ? ctx.assignmentSlot.toString() : ctx.messageId) + .map((ctx) => { + const id = ctx.messageId + + if (returnAssignmentSlot) { + const slot = ctx.assignmentSlot.toString() + if (returnMessageId) return { slot, id } + return slot + } + + return id + }) .bimap(errFrom, identity) .toPromise() } @@ -57,4 +69,4 @@ export function signedMessageWith (env) { .bimap(errFrom, identity) .toPromise() } -} \ No newline at end of file +} diff --git a/connect/src/lib/message/index.test.js b/connect/src/lib/message/index.test.js new file mode 100644 index 000000000..2ebbae044 --- /dev/null +++ b/connect/src/lib/message/index.test.js @@ -0,0 +1,62 @@ +import { describe, test } from 'node:test' +import * as assert from 'node:assert' + +import { createLogger } from '../../logger.js' +import { messageWith } from './index.js' + +const logger = createLogger('message') + +function createMessage (deployMessage) { + return messageWith({ deployMessage, logger }) +} + +describe('message', () => { + test('returns the message id by default', async () => { + const message = createMessage(async ({ returnMessageId }) => { + assert.equal(returnMessageId, undefined) + return { messageId: 'data-item-123', assignmentSlot: '42' } + }) + + const res = await message({ + process: 'process-asdf', + signer: () => {} + }) + + assert.equal(res, 'data-item-123') + }) + + test('returns the assignment slot when returnAssignmentSlot is true', async () => { + const message = createMessage(async ({ returnAssignmentSlot }) => { + assert.equal(returnAssignmentSlot, true) + return { messageId: 'data-item-123', assignmentSlot: '42' } + }) + + const res = await message({ + process: 'process-asdf', + signer: () => {}, + returnAssignmentSlot: true + }) + + assert.equal(res, '42') + }) + + test('returns slot and id when returnAssignmentSlot and returnMessageId are true', async () => { + const message = createMessage(async ({ returnAssignmentSlot, returnMessageId }) => { + assert.equal(returnAssignmentSlot, true) + assert.equal(returnMessageId, true) + return { messageId: 'data-item-123', assignmentSlot: '42' } + }) + + const res = await message({ + process: 'process-asdf', + signer: () => {}, + returnAssignmentSlot: true, + returnMessageId: true + }) + + assert.deepStrictEqual(res, { + slot: '42', + id: 'data-item-123' + }) + }) +}) diff --git a/connect/src/lib/message/upload-message.js b/connect/src/lib/message/upload-message.js index eacdb6a53..e8416b50b 100644 --- a/connect/src/lib/message/upload-message.js +++ b/connect/src/lib/message/upload-message.js @@ -115,14 +115,15 @@ export function uploadMessageWith (env) { return of(ctx) .chain(buildTags) .chain(buildData) - .chain(fromPromise(({ id, data, tags, anchor, signer, returnAssignmentSlot }) => { + .chain(fromPromise(({ id, data, tags, anchor, signer, returnAssignmentSlot, returnMessageId }) => { return deployMessage({ processId: id, data, tags, anchor, signer: signerSchema.implement(signer || env.signer), - returnAssignmentSlot + returnAssignmentSlot, + returnMessageId }) })) .map(res => ({