Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# JetBrains products (Webstorm, IntelliJ IDEA, ...)
.idea/
*.iml
.vscode

###############################################################################
# Build #
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import events from "events";
import { acceptContactRequest } from "./api/accept-contact-request";
import { addMemberToConversation } from "./api/add-member";
import { createConversation } from "./api/create-conversation";
import { declineContactRequest } from "./api/decline-contact-request";
import { getContact } from "./api/get-contact";
import { getConversation } from "./api/get-conversation";
import { getConversations } from "./api/get-conversations";
import { getJoinUrl } from "./api/get-join-url";
import { sendImage } from "./api/send-image";
import { sendMessage } from "./api/send-message";
import { setConversationTopic } from "./api/set-conversation-topic";
import { setStatus } from "./api/set-status";
import { ContactsInterface, ContactsService } from "./contacts/contacts";
import * as api from "./interfaces/api/api";
Expand All @@ -14,6 +18,7 @@ import { Context as ApiContext } from "./interfaces/api/context";
import { Conversation } from "./interfaces/api/conversation";
import * as apiEvents from "./interfaces/api/events";
import { HttpIo } from "./interfaces/http-io";
import { AllUsers } from "./interfaces/native-api/conversation";
import { MessagesPoller } from "./polling/messages-poller";
import { Contact } from "./types/contact";
import { Invite } from "./types/invite";
Expand Down Expand Up @@ -74,6 +79,22 @@ export class Api extends events.EventEmitter implements ApiEvents {
return sendMessage(this.io, this.context, message, conversationId);
}

async setConversationTopic(conversationId: string, topic: string): Promise<void> {
return setConversationTopic(this.io, this.context, conversationId, topic);
}

async getJoinUrl(conversationId: string): Promise<string> {
return getJoinUrl(this.io, this.context, conversationId);
}

async addMemberToConversation(conversationId: string, memberId: string): Promise<void> {
return addMemberToConversation(this.io, this.context, conversationId, memberId);
}

async createConversation(allUsers: AllUsers): Promise<any> {
return createConversation(this.io, this.context, allUsers);
}

async sendImage(message: api.NewImage, conversationId: string): Promise<api.SendMessageResult> {
return sendImage(this.io, this.context, message, conversationId);
}
Expand Down
37 changes: 37 additions & 0 deletions src/lib/api/add-member.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Incident } from "incident";
import { Context } from "../interfaces/api/context";
import * as io from "../interfaces/http-io";
import * as messagesUri from "../messages-uri";

interface RequestBody {
role: "User" | "Admin" | string;
}

export async function addMemberToConversation(
io: io.HttpIo,
apiContext: Context,
memberId: string,
converstionId: string,
role = "User",
): Promise<void> {

// `https://{host}}/v1/threads/${converstionId}/members/${memberId}`,
const uri: string = messagesUri.member(apiContext.registrationToken.host, converstionId, memberId);

const requestBody: RequestBody = { role };
const requestOptions: io.PutOptions = {
uri,
cookies: apiContext.cookies,
body: JSON.stringify(requestBody),
headers: {
"RegistrationToken": apiContext.registrationToken.raw,
"Content-type": "application/json",
},
};

const res: io.Response = await io.put(requestOptions);

if (res.statusCode !== 200) {
return Promise.reject(new Incident("add-member", "Received wrong return code"));
}
}
53 changes: 53 additions & 0 deletions src/lib/api/create-conversation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Incident } from "incident";
import { Context } from "../interfaces/api/context";
import * as io from "../interfaces/http-io";
import { AllUsers, Members } from "../interfaces/native-api/conversation";
import * as messagesUri from "../messages-uri";
import { getMembers } from "../utils";

interface RequestBody {
members: any[];
}

export async function createConversation(
io: io.HttpIo,
apiContext: Context,
allUsers: AllUsers,
): Promise<any> {

// Each member object consists of an ``id`` (user thread identifier), and role (either ``Admin`` or ``User``).
const members: Members[] = getMembers(allUsers);
const requestBody: RequestBody = {
members,
};

const uri: string = messagesUri.threads(apiContext.registrationToken.host);

const requestOptions: io.PostOptions = {
uri,
cookies: apiContext.cookies,
body: JSON.stringify(requestBody),
headers: {
RegistrationToken: apiContext.registrationToken.raw,
Location: "/",
},
};

const res: io.Response = await io.post(requestOptions);

if (res.statusCode !== 201) {
throw new Incident("create-conversation", "Received wrong return code");
}

const location: string | undefined = res.headers.location;
if (location === undefined) {
throw new Incident("create-conversation", "Missing `Location` response header");
}
// TODO: Parse URL properly / more reliable checks
const id: string | undefined = location.split("/").pop();
if (id === undefined) {
throw new Incident("create-conversation", "Unable to read conversation ID");
}
// conversation ID
return id;
}
37 changes: 37 additions & 0 deletions src/lib/api/get-join-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Incident } from "incident";
import { Context } from "../interfaces/api/context";
import * as io from "../interfaces/http-io";
import { Join } from "../interfaces/native-api/conversation";
import * as messagesUri from "../messages-uri";

interface RequestBody {
baseDomain: "https://join.skype.com/launch/" | string;
threadId: string;
}

export async function getJoinUrl(io: io.HttpIo, apiContext: Context, conversationId: string): Promise<string> {
const requestBody: RequestBody = {
baseDomain: "https://join.skype.com/launch/",
threadId: conversationId,
};

const uri: string = "https://api.scheduler.skype.com/threads";

const requestOptions: io.PostOptions = {
uri,
cookies: apiContext.cookies,
body: JSON.stringify(requestBody),
headers: {
"X-Skypetoken": apiContext.skypeToken.value,
"Content-Type": "application/json",
},
};

const res: io.Response = await io.post(requestOptions);
if (res.statusCode !== 200) {
return Promise.reject(new Incident("get-join-url", "Received wrong return code"));
}
const body: Join = JSON.parse(res.body);

return body.JoinUrl;
}
38 changes: 38 additions & 0 deletions src/lib/api/set-conversation-topic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Incident } from "incident";
import { Context } from "../interfaces/api/context";
import * as io from "../interfaces/http-io";
import * as messagesUri from "../messages-uri";

interface RequestBody {
topic: string;
}

export async function setConversationTopic(
io: io.HttpIo,
apiContext: Context,
conversationId: string,
topic: string,
): Promise<void> {

const requestBody: RequestBody = {
topic,
};

const uri: string = messagesUri.properties(apiContext.registrationToken.host, conversationId);

const requestOptions: io.PutOptions = {
uri,
cookies: apiContext.cookies,
body: JSON.stringify(requestBody),
queryString: {name: "topic"},
headers: {
"RegistrationToken": apiContext.registrationToken.raw,
"Content-type": "application/json",
},
};
const res: io.Response = await io.put(requestOptions);

if (res.statusCode !== 200) {
return Promise.reject(new Incident("set-conversation-topic", "Received wrong return code"));
}
}
17 changes: 17 additions & 0 deletions src/lib/interfaces/native-api/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export interface ThreadProperties {
version?: string;
}

// https://github.com/OllieTerrance/SkPy.docs/blob/master/protocol/chat.rst#join-urls
export interface Join {
Blob: string;
Id: string;
JoinUrl: string;
ThreadId: string;
}

export interface Conversation {
// https://{host}/v1/threads/{19:threadId} or // https://{host}/v1/users/ME/contacts/{8:contactId}
targetLink: string;
Expand Down Expand Up @@ -43,6 +51,15 @@ export interface ThreadMember {
friendlyName: string;
}

export interface AllUsers {
[type: string]: string[];
}

export interface Members {
id: string;
role: "Admin" | "User" | string;
}

export interface Thread {
// "19:..."
id: string;
Expand Down
27 changes: 27 additions & 0 deletions src/lib/messages-uri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ function buildThread(thread: string): string[] {
return buildThreads().concat(thread);
}

// /v1/threads/{thread}/properties
function buildProperties(thread: string): string[] {
return buildThread(thread).concat("properties");
}

// /v1/threads/{thread}/members
function buildMembers(thread: string): string[] {
return buildThread(thread).concat("members");
}

// /v1/threads/{thread}/members/{member}
function buildMember(thread: string, member: string): string[] {
return buildMembers(thread).concat(member);
}

// /v1/users
function buildUsers(): string[] {
return buildV1().concat("users");
Expand Down Expand Up @@ -133,10 +148,22 @@ function get(host: string, p: string) {
return url.resolve(getOrigin(host), p);
}

export function threads(host: string): string {
return get(host, joinPath(buildThreads()));
}

export function thread(host: string, threadId: string): string {
return get(host, joinPath(buildThread(threadId)));
}

export function member(host: string, threadId: string, member: string): string {
return get(host, joinPath(buildMember(threadId, member)));
}

export function properties(host: string, threadId: string): string {
return get(host, joinPath(buildProperties(threadId)));
}

export function users(host: string): string {
return get(host, joinPath(buildUsers()));
}
Expand Down
12 changes: 11 additions & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import _ from "lodash";

import { AllUsers, Members } from "./interfaces/native-api/conversation";
/**
* Returns the number of seconds since epoch.
*
Expand Down Expand Up @@ -98,3 +98,13 @@ export function parseHeaderParams(params: string): Map<string, string> {

return result;
}

export function getMembers(allUsers: AllUsers): Members[] {
return Object.keys(allUsers).reduce(
(acc: any[], key: string) => {
const role: "Admin" | "User" | string = key === "admins" ? "Admin" : "User";
const parsedGroup: Members[] = allUsers[key].map((id: string) => ({id, role}));

return [...acc, ...parsedGroup];
}, []);
}