spectrum-ts 1.1.1 → 1.2.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.
@@ -108,6 +108,87 @@ function mergeStreams(streams) {
108
108
  };
109
109
  });
110
110
  }
111
+ function broadcast(source) {
112
+ const consumers = /* @__PURE__ */ new Set();
113
+ let pumping = false;
114
+ let terminated = false;
115
+ let terminalError;
116
+ let pumpPromise;
117
+ let closed = false;
118
+ const startPump = () => {
119
+ if (pumping || terminated) {
120
+ return;
121
+ }
122
+ pumping = true;
123
+ pumpPromise = (async () => {
124
+ try {
125
+ for await (const value of source) {
126
+ for (const consumer of consumers) {
127
+ consumer.deliveries = consumer.deliveries.then(
128
+ () => consumer.emit(value).catch(() => {
129
+ })
130
+ );
131
+ }
132
+ }
133
+ terminated = true;
134
+ await Promise.allSettled(
135
+ Array.from(consumers, (consumer) => consumer.deliveries)
136
+ );
137
+ for (const consumer of consumers) {
138
+ consumer.end();
139
+ }
140
+ consumers.clear();
141
+ } catch (error) {
142
+ terminated = true;
143
+ terminalError = error;
144
+ for (const consumer of consumers) {
145
+ consumer.end(error);
146
+ }
147
+ consumers.clear();
148
+ }
149
+ })();
150
+ };
151
+ return {
152
+ subscribe() {
153
+ return stream((emit, end) => {
154
+ if (terminated) {
155
+ end(terminalError);
156
+ return;
157
+ }
158
+ const consumer = {
159
+ emit,
160
+ end,
161
+ deliveries: Promise.resolve()
162
+ };
163
+ consumers.add(consumer);
164
+ startPump();
165
+ return () => {
166
+ consumers.delete(consumer);
167
+ };
168
+ });
169
+ },
170
+ async close() {
171
+ if (closed) {
172
+ return;
173
+ }
174
+ closed = true;
175
+ try {
176
+ await source.close();
177
+ if (pumpPromise) {
178
+ await pumpPromise;
179
+ }
180
+ } finally {
181
+ if (!terminated) {
182
+ terminated = true;
183
+ for (const consumer of consumers) {
184
+ consumer.end();
185
+ }
186
+ consumers.clear();
187
+ }
188
+ }
189
+ }
190
+ };
191
+ }
111
192
 
112
193
  // src/utils/cloud.ts
113
194
  var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum.photon.codes"}`;
@@ -183,6 +264,7 @@ export {
183
264
  poll,
184
265
  stream,
185
266
  mergeStreams,
267
+ broadcast,
186
268
  SpectrumCloudError,
187
269
  cloud
188
270
  };
@@ -2,7 +2,7 @@ import {
2
2
  bufferToStream,
3
3
  readSchema,
4
4
  streamSchema
5
- } from "./chunk-7D6FHYKT.js";
5
+ } from "./chunk-UQPIWAHH.js";
6
6
 
7
7
  // src/content/voice.ts
8
8
  import { createReadStream } from "fs";
@@ -3,7 +3,7 @@ import {
3
3
  readSchema,
4
4
  resolveContents,
5
5
  streamSchema
6
- } from "./chunk-7D6FHYKT.js";
6
+ } from "./chunk-UQPIWAHH.js";
7
7
 
8
8
  // src/content/group.ts
9
9
  import z from "zod";
@@ -163,6 +163,7 @@ function richlink(url) {
163
163
 
164
164
  export {
165
165
  groupSchema,
166
+ asGroup,
166
167
  group,
167
168
  asRichlink,
168
169
  richlink
@@ -658,6 +658,43 @@ var warnUnsupported = (err, fallbackPlatform) => {
658
658
  supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
659
659
  );
660
660
  };
661
+ var contentPlatform = (content) => {
662
+ const platform = content.__platform;
663
+ return typeof platform === "string" ? platform : void 0;
664
+ };
665
+ var findUnsupportedPlatformContent = (content, platform) => {
666
+ const scopedPlatform = contentPlatform(content);
667
+ if (scopedPlatform && scopedPlatform !== platform) {
668
+ return scopedPlatform;
669
+ }
670
+ if (content.type !== "group") {
671
+ return;
672
+ }
673
+ for (const item of content.items) {
674
+ const nested = item.content;
675
+ if (typeof nested !== "object" || nested === null || !("type" in nested)) {
676
+ continue;
677
+ }
678
+ const unsupported = findUnsupportedPlatformContent(
679
+ nested,
680
+ platform
681
+ );
682
+ if (unsupported) {
683
+ return unsupported;
684
+ }
685
+ }
686
+ };
687
+ var unsupportedPlatformContentError = (content, platform) => {
688
+ const requiredPlatform = findUnsupportedPlatformContent(content, platform);
689
+ if (!requiredPlatform) {
690
+ return;
691
+ }
692
+ return UnsupportedError.content(
693
+ content.type,
694
+ platform,
695
+ `requires ${requiredPlatform}`
696
+ );
697
+ };
661
698
  var providerMessageCoreKeys = /* @__PURE__ */ new Set([
662
699
  "content",
663
700
  "id",
@@ -672,29 +709,36 @@ var extractExtras = (raw, definition) => {
672
709
  const extra = Object.fromEntries(entries);
673
710
  return definition.message?.schema ? definition.message.schema.parse(extra) : extra;
674
711
  };
675
- function wrapProviderMessage(raw, ctx) {
676
- const wrappedContent = wrapNestedContent(raw.content, ctx);
677
- return buildMessage({
712
+ function wrapProviderMessage(raw, ctx, direction) {
713
+ const wrappedContent = wrapNestedContent(raw.content, ctx, direction);
714
+ const base = {
678
715
  id: raw.id,
679
716
  content: wrappedContent,
680
- sender: raw.sender,
681
717
  timestamp: raw.timestamp ?? /* @__PURE__ */ new Date(),
682
718
  extras: extractExtras(raw, ctx.definition),
683
719
  spaceRef: ctx.spaceRef,
684
720
  space: ctx.space,
685
721
  definition: ctx.definition,
686
722
  client: ctx.client,
687
- config: ctx.config,
688
- direction: "inbound"
689
- });
723
+ config: ctx.config
724
+ };
725
+ if (direction === "inbound") {
726
+ if (!raw.sender) {
727
+ throw new Error(
728
+ `Inbound provider message missing sender (platform "${ctx.definition.name}", id "${raw.id}")`
729
+ );
730
+ }
731
+ return buildMessage({ ...base, sender: raw.sender, direction: "inbound" });
732
+ }
733
+ return buildMessage({ ...base, sender: raw.sender, direction: "outbound" });
690
734
  }
691
- var wrapNestedContent = (content, ctx) => {
735
+ var wrapNestedContent = (content, ctx, direction) => {
692
736
  if (content.type === "reaction") {
693
737
  const target = content.target;
694
738
  if (isRawProviderRecord(target)) {
695
739
  return {
696
740
  ...content,
697
- target: wrapProviderMessage(target, ctx)
741
+ target: wrapProviderMessage(target, ctx, "inbound")
698
742
  };
699
743
  }
700
744
  return content;
@@ -702,7 +746,10 @@ var wrapNestedContent = (content, ctx) => {
702
746
  if (content.type === "group") {
703
747
  const items = content.items.map((item) => {
704
748
  const raw = item;
705
- return isRawProviderRecord(raw) ? wrapProviderMessage(raw, ctx) : item;
749
+ if (!isRawProviderRecord(raw)) {
750
+ return item;
751
+ }
752
+ return direction === "inbound" ? wrapProviderMessage(raw, ctx, "inbound") : wrapProviderMessage(raw, ctx, "outbound");
706
753
  });
707
754
  return { ...content, items };
708
755
  }
@@ -739,9 +786,16 @@ function buildSpace(params) {
739
786
  }
740
787
  }
741
788
  async function dispatchSend(item) {
742
- let sendResult;
789
+ let raw;
743
790
  try {
744
- sendResult = await definition.actions.send({
791
+ const platformError = unsupportedPlatformContentError(
792
+ item,
793
+ definition.name
794
+ );
795
+ if (platformError) {
796
+ throw platformError;
797
+ }
798
+ raw = await definition.actions.send({
745
799
  ...typingCtx,
746
800
  content: item
747
801
  });
@@ -752,46 +806,16 @@ function buildSpace(params) {
752
806
  }
753
807
  throw err;
754
808
  }
755
- if (!sendResult?.id) {
809
+ if (!raw?.id) {
756
810
  throw new Error(
757
811
  `Platform "${definition.name}" send did not return a message id`
758
812
  );
759
813
  }
760
- const outboundContent = item.type === "group" && sendResult.groupMembers ? {
761
- ...item,
762
- items: item.items.map((stub, idx) => {
763
- const member = sendResult?.groupMembers?.[idx];
764
- if (!member?.id) {
765
- return stub;
766
- }
767
- return buildMessage({
768
- id: member.id,
769
- content: stub.content,
770
- sender: member.sender,
771
- timestamp: member.timestamp ?? /* @__PURE__ */ new Date(),
772
- extras: {},
773
- spaceRef,
774
- space,
775
- definition,
776
- client,
777
- config,
778
- direction: "outbound"
779
- });
780
- })
781
- } : item;
782
- return buildMessage({
783
- id: sendResult.id,
784
- content: outboundContent,
785
- sender: sendResult.sender,
786
- timestamp: sendResult.timestamp ?? /* @__PURE__ */ new Date(),
787
- extras: {},
788
- spaceRef,
789
- space,
790
- definition,
791
- client,
792
- config,
793
- direction: "outbound"
794
- });
814
+ return wrapProviderMessage(
815
+ raw,
816
+ { client, config, definition, space, spaceRef },
817
+ "outbound"
818
+ );
795
819
  }
796
820
  async function sendImpl(...content) {
797
821
  const resolved = await resolveContents(content);
@@ -837,13 +861,11 @@ function buildSpace(params) {
837
861
  if (!raw) {
838
862
  return;
839
863
  }
840
- return wrapProviderMessage(raw, {
841
- client,
842
- config,
843
- definition,
844
- space,
845
- spaceRef
846
- });
864
+ return wrapProviderMessage(
865
+ raw,
866
+ { client, config, definition, space, spaceRef },
867
+ "inbound"
868
+ );
847
869
  }
848
870
  space = {
849
871
  ...extras,
@@ -912,9 +934,16 @@ function buildMessage(params) {
912
934
  return self;
913
935
  };
914
936
  const dispatchReplyItem = async (item, target, replyToMessage) => {
915
- let sendResult;
937
+ let raw;
916
938
  try {
917
- sendResult = await replyToMessage({
939
+ const platformError = unsupportedPlatformContentError(
940
+ item,
941
+ definition.name
942
+ );
943
+ if (platformError) {
944
+ throw platformError;
945
+ }
946
+ raw = await replyToMessage({
918
947
  space: spaceRef,
919
948
  messageId: params.id,
920
949
  target,
@@ -929,24 +958,16 @@ function buildMessage(params) {
929
958
  }
930
959
  throw err;
931
960
  }
932
- if (!sendResult?.id) {
961
+ if (!raw?.id) {
933
962
  throw new Error(
934
963
  `Platform "${definition.name}" reply did not return a message id`
935
964
  );
936
965
  }
937
- return buildMessage({
938
- id: sendResult.id,
939
- content: item,
940
- sender: sendResult.sender,
941
- timestamp: sendResult.timestamp ?? /* @__PURE__ */ new Date(),
942
- extras: {},
943
- spaceRef,
944
- space,
945
- definition,
946
- client,
947
- config,
948
- direction: "outbound"
949
- });
966
+ return wrapProviderMessage(
967
+ raw,
968
+ { client, config, definition, space, spaceRef },
969
+ "outbound"
970
+ );
950
971
  };
951
972
  async function reply(...content) {
952
973
  const replyToMessage = definition.actions.replyToMessage;
@@ -993,6 +1014,14 @@ function buildMessage(params) {
993
1014
  if (!resolved) {
994
1015
  return;
995
1016
  }
1017
+ const platformError = unsupportedPlatformContentError(
1018
+ resolved,
1019
+ definition.name
1020
+ );
1021
+ if (platformError) {
1022
+ warnUnsupported(platformError, definition.name);
1023
+ return;
1024
+ }
996
1025
  try {
997
1026
  await definition.actions.editMessage({
998
1027
  space: spaceRef,
@@ -1146,6 +1175,14 @@ function createPlatformInstance(def, runtime) {
1146
1175
  });
1147
1176
  }
1148
1177
  }
1178
+ let messagesIterable;
1179
+ Object.defineProperty(base, "messages", {
1180
+ enumerable: true,
1181
+ get() {
1182
+ messagesIterable ??= runtime.subscribeMessages();
1183
+ return messagesIterable;
1184
+ }
1185
+ });
1149
1186
  return Object.assign(base, eventProperties);
1150
1187
  }
1151
1188
  function definePlatform(name, def) {
@@ -1205,6 +1242,18 @@ function definePlatform(name, def) {
1205
1242
  __definition: fullDef
1206
1243
  };
1207
1244
  };
1245
+ narrower.is = ((input) => {
1246
+ if (typeof input !== "object" || input === null) {
1247
+ return false;
1248
+ }
1249
+ if ("__platform" in input) {
1250
+ return input.__platform === name;
1251
+ }
1252
+ if ("platform" in input) {
1253
+ return input.platform === name;
1254
+ }
1255
+ return false;
1256
+ });
1208
1257
  if (def.static) {
1209
1258
  Object.assign(narrower, def.static);
1210
1259
  }
@@ -1215,6 +1264,7 @@ export {
1215
1264
  readSchema,
1216
1265
  streamSchema,
1217
1266
  bufferToStream,
1267
+ attachmentSchema,
1218
1268
  asAttachment,
1219
1269
  attachment,
1220
1270
  fromVCard,
@@ -1223,6 +1273,7 @@ export {
1223
1273
  contact,
1224
1274
  asCustom,
1225
1275
  custom,
1276
+ textSchema,
1226
1277
  asText,
1227
1278
  text,
1228
1279
  resolveContents,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { C as ContentBuilder, U as User, M as Message, a as ContentInput, b as Content, P as ProviderMessage, c as PlatformDef, d as Platform, e as PlatformProviderConfig, S as SpectrumLike, f as CustomEventStreams, g as Space, I as InboundMessage, O as OutboundMessage } from './types-BZhWdyLk.js';
2
- export { A as AnyPlatformDef, E as EventProducer, h as PlatformInstance, i as PlatformMessage, j as PlatformSpace, k as PlatformUser, l as SchemaMessage } from './types-BZhWdyLk.js';
1
+ import { C as ContentBuilder, U as User, M as Message, a as ContentInput, b as Content, P as ProviderMessage, c as PlatformDef, d as Platform, e as PlatformProviderConfig, S as SpectrumLike, f as CustomEventStreams, g as Space, I as InboundMessage, O as OutboundMessage } from './types-Dvp0I86h.js';
2
+ export { A as AnyPlatformDef, B as Broadcaster, E as EventProducer, h as InboundPlatformMessage, i as ManagedStream, j as PlatformInstance, k as PlatformMessage, l as PlatformRuntime, m as PlatformSpace, n as PlatformUser, o as SchemaMessage, p as broadcast, q as mergeStreams, s as stream } from './types-Dvp0I86h.js';
3
3
  import vCard from 'vcf';
4
4
  import z__default from 'zod';
5
- export { M as ManagedStream, m as mergeStreams, s as stream } from './stream-B55k7W8-.js';
6
5
  import 'hotscript';
7
6
 
8
7
  declare function attachment(input: string | Buffer, options?: {
package/dist/index.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  group,
3
3
  richlink
4
- } from "./chunk-TY3RT4OB.js";
4
+ } from "./chunk-K3CTEGCZ.js";
5
5
  import {
6
6
  voice
7
- } from "./chunk-7VSE6V3Q.js";
7
+ } from "./chunk-I7EKZS5C.js";
8
8
  import {
9
9
  SpectrumCloudError,
10
+ broadcast,
10
11
  cloud,
11
12
  mergeStreams,
12
13
  option,
13
14
  poll,
14
15
  stream
15
- } from "./chunk-PXX7ISZ6.js";
16
+ } from "./chunk-FF2R4EP3.js";
16
17
  import {
17
18
  UnsupportedError,
18
19
  attachment,
@@ -26,7 +27,7 @@ import {
26
27
  text,
27
28
  toVCard,
28
29
  wrapProviderMessage
29
- } from "./chunk-7D6FHYKT.js";
30
+ } from "./chunk-UQPIWAHH.js";
30
31
 
31
32
  // src/emoji/generated.ts
32
33
  var GeneratedEmoji = {
@@ -1986,23 +1987,9 @@ async function Spectrum(options) {
1986
1987
  } = options;
1987
1988
  const flattenGroups = runtimeOptions?.flattenGroups ?? false;
1988
1989
  const platformStates = /* @__PURE__ */ new Map();
1990
+ const messageBroadcasters = /* @__PURE__ */ new Map();
1989
1991
  const customEventStreams = /* @__PURE__ */ new Map();
1990
1992
  let stopped = false;
1991
- for (const provider of providers) {
1992
- const providerConfig = provider;
1993
- const def = providerConfig.__definition;
1994
- const userConfig = def.config.parse(providerConfig.config);
1995
- const client = await def.lifecycle.createClient({
1996
- config: userConfig,
1997
- projectId,
1998
- projectSecret
1999
- });
2000
- platformStates.set(def.name, {
2001
- client,
2002
- config: userConfig,
2003
- definition: def
2004
- });
2005
- }
2006
1993
  const adaptIterable = (iterable) => {
2007
1994
  return stream((emit, end) => {
2008
1995
  const iterator = iterable[Symbol.asyncIterator]();
@@ -2045,13 +2032,17 @@ async function Spectrum(options) {
2045
2032
  client,
2046
2033
  config
2047
2034
  });
2048
- const normalizedMessage = wrapProviderMessage(msg, {
2049
- client,
2050
- config,
2051
- definition,
2052
- space,
2053
- spaceRef
2054
- });
2035
+ const normalizedMessage = wrapProviderMessage(
2036
+ msg,
2037
+ {
2038
+ client,
2039
+ config,
2040
+ definition,
2041
+ space,
2042
+ spaceRef
2043
+ },
2044
+ "inbound"
2045
+ );
2055
2046
  if (flattenGroups && normalizedMessage.content.type === "group") {
2056
2047
  for (const item of normalizedMessage.content.items) {
2057
2048
  yield [space, item];
@@ -2063,10 +2054,46 @@ async function Spectrum(options) {
2063
2054
  };
2064
2055
  return adaptIterable(bindSend());
2065
2056
  };
2057
+ const getOrCreateMessageBroadcast = (state) => {
2058
+ if (stopped) {
2059
+ throw new Error(
2060
+ `Spectrum instance has been stopped; cannot subscribe to "${state.definition.name}" messages`
2061
+ );
2062
+ }
2063
+ const name = state.definition.name;
2064
+ let broadcaster = messageBroadcasters.get(name);
2065
+ if (!broadcaster) {
2066
+ broadcaster = broadcast(createProviderMessagesStream(state));
2067
+ messageBroadcasters.set(name, broadcaster);
2068
+ }
2069
+ return broadcaster;
2070
+ };
2071
+ for (const provider of providers) {
2072
+ const providerConfig = provider;
2073
+ const def = providerConfig.__definition;
2074
+ const userConfig = def.config.parse(providerConfig.config);
2075
+ const client = await def.lifecycle.createClient({
2076
+ config: userConfig,
2077
+ projectId,
2078
+ projectSecret
2079
+ });
2080
+ const state = {
2081
+ client,
2082
+ config: userConfig,
2083
+ definition: def
2084
+ };
2085
+ platformStates.set(def.name, {
2086
+ ...state,
2087
+ subscribeMessages: () => getOrCreateMessageBroadcast(state).subscribe()
2088
+ });
2089
+ }
2066
2090
  const createMessagesStream = () => {
2067
2091
  return stream((emit, end) => {
2068
2092
  const merged = mergeStreams(
2069
- Array.from(platformStates.values(), createProviderMessagesStream)
2093
+ Array.from(
2094
+ platformStates.values(),
2095
+ (runtime) => runtime.subscribeMessages()
2096
+ )
2070
2097
  );
2071
2098
  const pump = (async () => {
2072
2099
  try {
@@ -2130,6 +2157,10 @@ async function Spectrum(options) {
2130
2157
  ...Array.from(
2131
2158
  customEventStreams.values(),
2132
2159
  (eventStream) => eventStream.close()
2160
+ ),
2161
+ ...Array.from(
2162
+ messageBroadcasters.values(),
2163
+ (broadcaster) => broadcaster.close()
2133
2164
  )
2134
2165
  ];
2135
2166
  process.off("SIGINT", handleSignal);
@@ -2143,6 +2174,7 @@ async function Spectrum(options) {
2143
2174
  );
2144
2175
  await Promise.allSettled(clientShutdowns);
2145
2176
  customEventStreams.clear();
2177
+ messageBroadcasters.clear();
2146
2178
  platformStates.clear();
2147
2179
  };
2148
2180
  const handleSignal = () => {
@@ -2200,6 +2232,7 @@ export {
2200
2232
  SpectrumCloudError,
2201
2233
  UnsupportedError,
2202
2234
  attachment,
2235
+ broadcast,
2203
2236
  cloud,
2204
2237
  contact,
2205
2238
  custom,
@@ -1,12 +1,14 @@
1
- import { M as ManagedStream } from '../../stream-B55k7W8-.js';
2
- import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-BZhWdyLk.js';
1
+ import { a as ContentInput, C as ContentBuilder, o as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage, i as ManagedStream } from '../../types-Dvp0I86h.js';
3
2
  import * as zod_v4_core from 'zod/v4/core';
4
3
  import * as z from 'zod';
5
4
  import z__default from 'zod';
6
- import { AdvancedIMessage } from '@photon-ai/advanced-imessage';
5
+ import { MessageEffect, AdvancedIMessage } from '@photon-ai/advanced-imessage';
7
6
  import { IMessageSDK } from '@photon-ai/imessage-kit';
8
7
  import 'hotscript';
9
8
 
9
+ type IMessageMessageEffect = MessageEffect;
10
+ declare function effect(input: ContentInput, messageEffect: IMessageMessageEffect): ContentBuilder;
11
+
10
12
  type IMessageClient = IMessageSDK | AdvancedIMessage[];
11
13
  declare const userSchema: z__default.ZodObject<{}, z__default.core.$strip>;
12
14
  declare const spaceSchema: z__default.ZodObject<{
@@ -76,6 +78,24 @@ declare const imessage: Platform<PlatformDef<"iMessage", z.ZodUnion<readonly [z.
76
78
  }[] | undefined;
77
79
  };
78
80
  }) => ManagedStream<IMessageMessage>;
79
- }>> & Readonly<Record<never, never>>;
81
+ }>> & Readonly<{
82
+ effect: {
83
+ message: {
84
+ readonly slam: "com.apple.MobileSMS.expressivesend.impact";
85
+ readonly loud: "com.apple.MobileSMS.expressivesend.loud";
86
+ readonly gentle: "com.apple.MobileSMS.expressivesend.gentle";
87
+ readonly invisible: "com.apple.MobileSMS.expressivesend.invisibleink";
88
+ readonly confetti: "com.apple.messages.effect.CKConfettiEffect";
89
+ readonly fireworks: "com.apple.messages.effect.CKFireworksEffect";
90
+ readonly balloons: "com.apple.messages.effect.CKBalloonEffect";
91
+ readonly heart: "com.apple.messages.effect.CKHeartEffect";
92
+ readonly lasers: "com.apple.messages.effect.CKLasersEffect";
93
+ readonly celebration: "com.apple.messages.effect.CKHappyBirthdayEffect";
94
+ readonly sparkles: "com.apple.messages.effect.CKSparklesEffect";
95
+ readonly spotlight: "com.apple.messages.effect.CKSpotlightEffect";
96
+ readonly echo: "com.apple.messages.effect.CKEchoEffect";
97
+ };
98
+ };
99
+ }>;
80
100
 
81
- export { imessage };
101
+ export { type IMessageMessageEffect, effect, imessage };