Skip to content

Commit f066982

Browse files
grigori-grudemurgos
authored andcommitted
Add createConversation, setConversationTopic, getJoinUrl, addMember
* add create-conversation, set-topic, get-join-url * addMember added * Minor fixes to createConversation - Use `messagesUri.threads` instead of passing empty threadId to `messagesUri.thread`. - Check for existence of location header and conversation id.
1 parent f216476 commit f066982

10 files changed

+250
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# JetBrains products (Webstorm, IntelliJ IDEA, ...)
55
.idea/
66
*.iml
7+
.vscode
78

89
###############################################################################
910
# Build #

package-lock.json

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/api.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import events from "events";
22
import { acceptContactRequest } from "./api/accept-contact-request";
3+
import { addMemberToConversation } from "./api/add-member";
4+
import { createConversation } from "./api/create-conversation";
35
import { declineContactRequest } from "./api/decline-contact-request";
46
import { getContact } from "./api/get-contact";
57
import { getConversation } from "./api/get-conversation";
68
import { getConversations } from "./api/get-conversations";
9+
import { getJoinUrl } from "./api/get-join-url";
710
import { sendImage } from "./api/send-image";
811
import { sendMessage } from "./api/send-message";
12+
import { setConversationTopic } from "./api/set-conversation-topic";
913
import { setStatus } from "./api/set-status";
1014
import { ContactsInterface, ContactsService } from "./contacts/contacts";
1115
import * as api from "./interfaces/api/api";
@@ -14,6 +18,7 @@ import { Context as ApiContext } from "./interfaces/api/context";
1418
import { Conversation } from "./interfaces/api/conversation";
1519
import * as apiEvents from "./interfaces/api/events";
1620
import { HttpIo } from "./interfaces/http-io";
21+
import { AllUsers } from "./interfaces/native-api/conversation";
1722
import { MessagesPoller } from "./polling/messages-poller";
1823
import { Contact } from "./types/contact";
1924
import { Invite } from "./types/invite";
@@ -74,6 +79,22 @@ export class Api extends events.EventEmitter implements ApiEvents {
7479
return sendMessage(this.io, this.context, message, conversationId);
7580
}
7681

82+
async setConversationTopic(conversationId: string, topic: string): Promise<void> {
83+
return setConversationTopic(this.io, this.context, conversationId, topic);
84+
}
85+
86+
async getJoinUrl(conversationId: string): Promise<string> {
87+
return getJoinUrl(this.io, this.context, conversationId);
88+
}
89+
90+
async addMemberToConversation(conversationId: string, memberId: string): Promise<void> {
91+
return addMemberToConversation(this.io, this.context, conversationId, memberId);
92+
}
93+
94+
async createConversation(allUsers: AllUsers): Promise<any> {
95+
return createConversation(this.io, this.context, allUsers);
96+
}
97+
7798
async sendImage(message: api.NewImage, conversationId: string): Promise<api.SendMessageResult> {
7899
return sendImage(this.io, this.context, message, conversationId);
79100
}

src/lib/api/add-member.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Incident } from "incident";
2+
import { Context } from "../interfaces/api/context";
3+
import * as io from "../interfaces/http-io";
4+
import * as messagesUri from "../messages-uri";
5+
6+
interface RequestBody {
7+
role: "User" | "Admin" | string;
8+
}
9+
10+
export async function addMemberToConversation(
11+
io: io.HttpIo,
12+
apiContext: Context,
13+
memberId: string,
14+
converstionId: string,
15+
role = "User",
16+
): Promise<void> {
17+
18+
// `https://{host}}/v1/threads/${converstionId}/members/${memberId}`,
19+
const uri: string = messagesUri.member(apiContext.registrationToken.host, converstionId, memberId);
20+
21+
const requestBody: RequestBody = { role };
22+
const requestOptions: io.PutOptions = {
23+
uri,
24+
cookies: apiContext.cookies,
25+
body: JSON.stringify(requestBody),
26+
headers: {
27+
"RegistrationToken": apiContext.registrationToken.raw,
28+
"Content-type": "application/json",
29+
},
30+
};
31+
32+
const res: io.Response = await io.put(requestOptions);
33+
34+
if (res.statusCode !== 200) {
35+
return Promise.reject(new Incident("add-member", "Received wrong return code"));
36+
}
37+
}

src/lib/api/create-conversation.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Incident } from "incident";
2+
import { Context } from "../interfaces/api/context";
3+
import * as io from "../interfaces/http-io";
4+
import { AllUsers, Members } from "../interfaces/native-api/conversation";
5+
import * as messagesUri from "../messages-uri";
6+
import { getMembers } from "../utils";
7+
8+
interface RequestBody {
9+
members: any[];
10+
}
11+
12+
export async function createConversation(
13+
io: io.HttpIo,
14+
apiContext: Context,
15+
allUsers: AllUsers,
16+
): Promise<any> {
17+
18+
// Each member object consists of an ``id`` (user thread identifier), and role (either ``Admin`` or ``User``).
19+
const members: Members[] = getMembers(allUsers);
20+
const requestBody: RequestBody = {
21+
members,
22+
};
23+
24+
const uri: string = messagesUri.threads(apiContext.registrationToken.host);
25+
26+
const requestOptions: io.PostOptions = {
27+
uri,
28+
cookies: apiContext.cookies,
29+
body: JSON.stringify(requestBody),
30+
headers: {
31+
RegistrationToken: apiContext.registrationToken.raw,
32+
Location: "/",
33+
},
34+
};
35+
36+
const res: io.Response = await io.post(requestOptions);
37+
38+
if (res.statusCode !== 201) {
39+
throw new Incident("create-conversation", "Received wrong return code");
40+
}
41+
42+
const location: string | undefined = res.headers.location;
43+
if (location === undefined) {
44+
throw new Incident("create-conversation", "Missing `Location` response header");
45+
}
46+
// TODO: Parse URL properly / more reliable checks
47+
const id: string | undefined = location.split("/").pop();
48+
if (id === undefined) {
49+
throw new Incident("create-conversation", "Unable to read conversation ID");
50+
}
51+
// conversation ID
52+
return id;
53+
}

src/lib/api/get-join-url.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Incident } from "incident";
2+
import { Context } from "../interfaces/api/context";
3+
import * as io from "../interfaces/http-io";
4+
import { Join } from "../interfaces/native-api/conversation";
5+
import * as messagesUri from "../messages-uri";
6+
7+
interface RequestBody {
8+
baseDomain: "https://join.skype.com/launch/" | string;
9+
threadId: string;
10+
}
11+
12+
export async function getJoinUrl(io: io.HttpIo, apiContext: Context, conversationId: string): Promise<string> {
13+
const requestBody: RequestBody = {
14+
baseDomain: "https://join.skype.com/launch/",
15+
threadId: conversationId,
16+
};
17+
18+
const uri: string = "https://api.scheduler.skype.com/threads";
19+
20+
const requestOptions: io.PostOptions = {
21+
uri,
22+
cookies: apiContext.cookies,
23+
body: JSON.stringify(requestBody),
24+
headers: {
25+
"X-Skypetoken": apiContext.skypeToken.value,
26+
"Content-Type": "application/json",
27+
},
28+
};
29+
30+
const res: io.Response = await io.post(requestOptions);
31+
if (res.statusCode !== 200) {
32+
return Promise.reject(new Incident("get-join-url", "Received wrong return code"));
33+
}
34+
const body: Join = JSON.parse(res.body);
35+
36+
return body.JoinUrl;
37+
}

src/lib/api/set-conversation-topic.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Incident } from "incident";
2+
import { Context } from "../interfaces/api/context";
3+
import * as io from "../interfaces/http-io";
4+
import * as messagesUri from "../messages-uri";
5+
6+
interface RequestBody {
7+
topic: string;
8+
}
9+
10+
export async function setConversationTopic(
11+
io: io.HttpIo,
12+
apiContext: Context,
13+
conversationId: string,
14+
topic: string,
15+
): Promise<void> {
16+
17+
const requestBody: RequestBody = {
18+
topic,
19+
};
20+
21+
const uri: string = messagesUri.properties(apiContext.registrationToken.host, conversationId);
22+
23+
const requestOptions: io.PutOptions = {
24+
uri,
25+
cookies: apiContext.cookies,
26+
body: JSON.stringify(requestBody),
27+
queryString: {name: "topic"},
28+
headers: {
29+
"RegistrationToken": apiContext.registrationToken.raw,
30+
"Content-type": "application/json",
31+
},
32+
};
33+
const res: io.Response = await io.put(requestOptions);
34+
35+
if (res.statusCode !== 200) {
36+
return Promise.reject(new Incident("set-conversation-topic", "Received wrong return code"));
37+
}
38+
}

src/lib/interfaces/native-api/conversation.ts

+17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ export interface ThreadProperties {
88
version?: string;
99
}
1010

11+
// https://github.com/OllieTerrance/SkPy.docs/blob/master/protocol/chat.rst#join-urls
12+
export interface Join {
13+
Blob: string;
14+
Id: string;
15+
JoinUrl: string;
16+
ThreadId: string;
17+
}
18+
1119
export interface Conversation {
1220
// https://{host}/v1/threads/{19:threadId} or // https://{host}/v1/users/ME/contacts/{8:contactId}
1321
targetLink: string;
@@ -43,6 +51,15 @@ export interface ThreadMember {
4351
friendlyName: string;
4452
}
4553

54+
export interface AllUsers {
55+
[type: string]: string[];
56+
}
57+
58+
export interface Members {
59+
id: string;
60+
role: "Admin" | "User" | string;
61+
}
62+
4663
export interface Thread {
4764
// "19:..."
4865
id: string;

src/lib/messages-uri.ts

+27
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ function buildThread(thread: string): string[] {
3131
return buildThreads().concat(thread);
3232
}
3333

34+
// /v1/threads/{thread}/properties
35+
function buildProperties(thread: string): string[] {
36+
return buildThread(thread).concat("properties");
37+
}
38+
39+
// /v1/threads/{thread}/members
40+
function buildMembers(thread: string): string[] {
41+
return buildThread(thread).concat("members");
42+
}
43+
44+
// /v1/threads/{thread}/members/{member}
45+
function buildMember(thread: string, member: string): string[] {
46+
return buildMembers(thread).concat(member);
47+
}
48+
3449
// /v1/users
3550
function buildUsers(): string[] {
3651
return buildV1().concat("users");
@@ -133,10 +148,22 @@ function get(host: string, p: string) {
133148
return url.resolve(getOrigin(host), p);
134149
}
135150

151+
export function threads(host: string): string {
152+
return get(host, joinPath(buildThreads()));
153+
}
154+
136155
export function thread(host: string, threadId: string): string {
137156
return get(host, joinPath(buildThread(threadId)));
138157
}
139158

159+
export function member(host: string, threadId: string, member: string): string {
160+
return get(host, joinPath(buildMember(threadId, member)));
161+
}
162+
163+
export function properties(host: string, threadId: string): string {
164+
return get(host, joinPath(buildProperties(threadId)));
165+
}
166+
140167
export function users(host: string): string {
141168
return get(host, joinPath(buildUsers()));
142169
}

src/lib/utils.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import _ from "lodash";
2-
2+
import { AllUsers, Members } from "./interfaces/native-api/conversation";
33
/**
44
* Returns the number of seconds since epoch.
55
*
@@ -98,3 +98,13 @@ export function parseHeaderParams(params: string): Map<string, string> {
9898

9999
return result;
100100
}
101+
102+
export function getMembers(allUsers: AllUsers): Members[] {
103+
return Object.keys(allUsers).reduce(
104+
(acc: any[], key: string) => {
105+
const role: "Admin" | "User" | string = key === "admins" ? "Admin" : "User";
106+
const parsedGroup: Members[] = allUsers[key].map((id: string) => ({id, role}));
107+
108+
return [...acc, ...parsedGroup];
109+
}, []);
110+
}

0 commit comments

Comments
 (0)