Skip to content

Commit 20aa9ee

Browse files
authored
Merge pull request #10 from photon-hq/refactor/api
feat: support poll
2 parents 525b8e7 + 03f5896 commit 20aa9ee

8 files changed

Lines changed: 213 additions & 2 deletions

File tree

client.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
HandleModule,
1212
ICloudModule,
1313
MessageModule,
14+
PollModule,
1415
ScheduledMessageModule,
1516
ServerModule,
1617
} from "./modules";
@@ -47,6 +48,7 @@ export class AdvancedIMessageKit extends EventEmitter implements TypedEventEmitt
4748
public readonly facetime: FaceTimeModule;
4849
public readonly icloud: ICloudModule;
4950

51+
public readonly polls: PollModule;
5052
public readonly scheduledMessages: ScheduledMessageModule;
5153
public readonly server: ServerModule;
5254

@@ -63,6 +65,12 @@ export class AdvancedIMessageKit extends EventEmitter implements TypedEventEmitt
6365
// a single user/SDK instance are sent in strict order, preventing race conditions.
6466
private sendQueue: Promise<unknown> = Promise.resolve();
6567

68+
// Flag to track if 'ready' event has been emitted
69+
//
70+
// Purpose: Prevent duplicate 'ready' events when both legacy mode (no API key)
71+
// and auth-ok events occur, which would cause user callbacks to fire twice.
72+
private readyEmitted = false;
73+
6674
private constructor(config: ClientConfig = {}) {
6775
super();
6876

@@ -114,6 +122,7 @@ export class AdvancedIMessageKit extends EventEmitter implements TypedEventEmitt
114122
this.facetime = new FaceTimeModule(this.http);
115123
this.icloud = new ICloudModule(this.http);
116124

125+
this.polls = new PollModule(this.http);
117126
this.scheduledMessages = new ScheduledMessageModule(this.http);
118127
this.server = new ServerModule(this.http);
119128
}
@@ -218,13 +227,17 @@ export class AdvancedIMessageKit extends EventEmitter implements TypedEventEmitt
218227

219228
this.socket.on("disconnect", () => {
220229
this.logger.info("Disconnected from iMessage server");
230+
this.readyEmitted = false;
221231
this.emit("disconnect");
222232
});
223233

224234
// Listen for authentication success
225235
this.socket.on("auth-ok", () => {
226236
this.logger.info("Authentication successful");
227-
this.emit("ready");
237+
if (!this.readyEmitted) {
238+
this.readyEmitted = true;
239+
this.emit("ready");
240+
}
228241
});
229242

230243
// Listen for authentication errors
@@ -243,7 +256,10 @@ export class AdvancedIMessageKit extends EventEmitter implements TypedEventEmitt
243256
// If no apiKey, assume legacy server that doesn't require auth - emit ready immediately
244257
if (!this.config.apiKey) {
245258
this.logger.info("No API key provided, skipping authentication (legacy server mode)");
246-
this.emit("ready");
259+
if (!this.readyEmitted) {
260+
this.readyEmitted = true;
261+
this.emit("ready");
262+
}
247263
}
248264
});
249265

examples/poll-add-option.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createSDK, handleError } from "./utils";
2+
3+
const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890";
4+
5+
async function main() {
6+
const sdk = createSDK();
7+
8+
sdk.on("ready", async () => {
9+
console.log("Poll creation and add option example...\n");
10+
11+
try {
12+
console.log("Step 1: Creating a poll with 2 options...");
13+
const pollMessage = await sdk.polls.create({
14+
chatGuid: CHAT_GUID,
15+
title: "",
16+
options: ["Option A", "Option B"],
17+
});
18+
19+
console.log("✓ Poll created!");
20+
console.log(`Poll GUID: ${pollMessage.guid}`);
21+
22+
await new Promise((resolve) => setTimeout(resolve, 2000));
23+
24+
console.log("\nStep 2: Adding a new option...");
25+
const editMessage = await sdk.polls.addOption({
26+
chatGuid: CHAT_GUID,
27+
pollMessageGuid: pollMessage.guid,
28+
optionText: "Option C - Added Later",
29+
});
30+
31+
console.log("✓ Option added!");
32+
console.log(`Edit message GUID: ${editMessage.guid}`);
33+
console.log(`Associated Message Type: ${editMessage.associatedMessageType}`);
34+
} catch (error) {
35+
handleError(error, "Poll operation failed");
36+
}
37+
38+
await sdk.close();
39+
process.exit(0);
40+
});
41+
42+
await sdk.connect();
43+
}
44+
45+
main().catch(console.error);

examples/poll-create.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createSDK, handleError } from "./utils";
2+
3+
const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890";
4+
5+
async function main() {
6+
const sdk = createSDK();
7+
8+
sdk.on("ready", async () => {
9+
console.log("Poll creation example...\n");
10+
11+
try {
12+
console.log("Creating a poll...");
13+
const pollMessage = await sdk.polls.create({
14+
chatGuid: CHAT_GUID,
15+
title: "",
16+
options: [
17+
"Option A - First choice",
18+
"Option B - Second choice",
19+
"Option C - Third choice",
20+
"Option D - Fourth choice",
21+
],
22+
});
23+
24+
console.log("\n✓ Poll created successfully!");
25+
console.log(`Poll message GUID: ${pollMessage.guid}`);
26+
console.log(`Balloon Bundle ID: ${pollMessage.balloonBundleId}`);
27+
28+
if (pollMessage.payloadData) {
29+
console.log(`\nPayload Data: ${JSON.stringify(pollMessage.payloadData, null, 2)}`);
30+
}
31+
} catch (error) {
32+
handleError(error, "Failed to create poll");
33+
}
34+
35+
await sdk.close();
36+
process.exit(0);
37+
});
38+
39+
await sdk.connect();
40+
}
41+
42+
main().catch(console.error);

examples/test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
123456

modules/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export * from "./facetime";
55
export * from "./handle";
66
export * from "./icloud";
77
export * from "./message";
8+
export * from "./poll";
89
export * from "./scheduled";
910
export * from "./server";

modules/poll.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { AxiosInstance } from "axios";
2+
import type { AddPollOptionOptions, CreatePollOptions, PollMessageResponse, VotePollOptions } from "../types/poll";
3+
4+
export class PollModule {
5+
constructor(private readonly http: AxiosInstance) {}
6+
7+
async create(options: CreatePollOptions): Promise<PollMessageResponse> {
8+
if (options.options.length < 2) {
9+
throw new Error("Poll must have at least 2 options");
10+
}
11+
12+
const { data } = await this.http.post("/api/v1/poll/create", {
13+
chatGuid: options.chatGuid,
14+
title: options.title ?? "",
15+
options: options.options,
16+
});
17+
18+
return data.data;
19+
}
20+
21+
async vote(options: VotePollOptions): Promise<PollMessageResponse> {
22+
const { data } = await this.http.post("/api/v1/poll/vote", {
23+
chatGuid: options.chatGuid,
24+
pollMessageGuid: options.pollMessageGuid,
25+
optionIdentifier: options.optionIdentifier,
26+
});
27+
28+
return data.data;
29+
}
30+
31+
async unvote(options: VotePollOptions): Promise<PollMessageResponse> {
32+
const { data } = await this.http.post("/api/v1/poll/unvote", {
33+
chatGuid: options.chatGuid,
34+
pollMessageGuid: options.pollMessageGuid,
35+
optionIdentifier: options.optionIdentifier,
36+
});
37+
38+
return data.data;
39+
}
40+
41+
async addOption(options: AddPollOptionOptions): Promise<PollMessageResponse> {
42+
if (!options.optionText || options.optionText.trim().length === 0) {
43+
throw new Error("Option text cannot be empty");
44+
}
45+
46+
const { data } = await this.http.post("/api/v1/poll/option", {
47+
chatGuid: options.chatGuid,
48+
pollMessageGuid: options.pollMessageGuid,
49+
optionText: options.optionText,
50+
});
51+
52+
return data.data;
53+
}
54+
}

types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from "./facetime";
66
export * from "./findmy";
77
export * from "./handle";
88
export * from "./message";
9+
export * from "./poll";
910
export * from "./scheduled";
1011
export * from "./server";
1112
export * from "./sticker";

types/poll.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { MessageResponse } from "./message";
2+
3+
export interface CreatePollOptions {
4+
chatGuid: string;
5+
title?: string;
6+
options: string[];
7+
}
8+
9+
export interface VotePollOptions {
10+
chatGuid: string;
11+
pollMessageGuid: string;
12+
optionIdentifier: string;
13+
}
14+
15+
export interface AddPollOptionOptions {
16+
chatGuid: string;
17+
pollMessageGuid: string;
18+
optionText: string;
19+
}
20+
21+
export interface PollOption {
22+
optionIdentifier: string;
23+
text: string;
24+
attributedText: string;
25+
creatorHandle: string;
26+
canBeEdited: boolean;
27+
}
28+
29+
export interface PollVote {
30+
voteOptionIdentifier: string;
31+
participantHandle: string;
32+
serverVoteTime?: number;
33+
}
34+
35+
export interface PollDefinition {
36+
version: number;
37+
item: {
38+
title: string;
39+
orderedPollOptions: PollOption[];
40+
creatorHandle: string;
41+
};
42+
}
43+
44+
export interface PollVoteResponse {
45+
version: number;
46+
item: {
47+
votes: PollVote[];
48+
};
49+
}
50+
51+
export type PollMessageResponse = MessageResponse;

0 commit comments

Comments
 (0)