Skip to content

Commit

Permalink
typescript faye messages (#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
Amin Mahboubi authored Mar 15, 2021
1 parent b7f1445 commit 869ffa8
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 197 deletions.
134 changes: 102 additions & 32 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require('../package.json');

export type UnknownRecord = Record<string, unknown>;
export type UR = Record<string, unknown>;
export type UnknownRecord = UR; // alias to avoid breaking change

export type APIResponse = { duration?: string };

Expand Down Expand Up @@ -98,32 +99,43 @@ type AxiosConfig = {
url: string;
axiosOptions?: axios.AxiosRequestConfig;
body?: unknown;
headers?: UnknownRecord;
qs?: UnknownRecord;
headers?: UR;
qs?: UR;
serviceName?: string;
};

export type HandlerCallback = (...args: unknown[]) => unknown;

export type ForeignIDTimes = { foreignID: string; time: Date | string };

export type ActivityPartialChanges<ActivityType extends UnknownRecord = UnknownRecord> = Partial<ForeignIDTimes> & {
export type ActivityPartialChanges<ActivityType extends UR = UR> = Partial<ForeignIDTimes> & {
id?: string;
set?: Partial<ActivityType>;
unset?: Array<Extract<keyof ActivityType, string>>;
};

export type RealTimeMessage<UserType extends UR = UR, ActivityType extends UR = UR> = {
deleted: Array<string>;
deleted_foreign_ids: Array<[id: string, time: string]>;

This comment has been minimized.

Copy link
@klamparski

klamparski Apr 6, 2021

Shouldn't this typing be Array<{id: string, time: string}>;? In current state it brings errors, at least for TS 3.8.x.

new: Array<Omit<Activity<ActivityType>, 'actor'> & { actor: string | UserType }>;
app_id?: string;
feed?: string;
mark_read?: 'all' | 'current' | Array<string>;
mark_seen?: 'all' | 'current' | Array<string>;
published_at?: string;
};

/**
* Client to connect to Stream api
* @class StreamClient
*/
export class StreamClient<
UserType extends UnknownRecord = UnknownRecord,
ActivityType extends UnknownRecord = UnknownRecord,
CollectionType extends UnknownRecord = UnknownRecord,
ReactionType extends UnknownRecord = UnknownRecord,
ChildReactionType extends UnknownRecord = UnknownRecord,
PersonalizationType extends UnknownRecord = UnknownRecord
UserType extends UR = UR,
ActivityType extends UR = UR,
CollectionType extends UR = UR,
ReactionType extends UR = UR,
ChildReactionType extends UR = UR,
PersonalizationType extends UR = UR
> {
baseUrl: string;
baseAnalyticsUrl: string;
Expand All @@ -141,7 +153,7 @@ export class StreamClient<
group: string;
expireTokens: boolean;
location: string;
fayeClient: Faye.Client | null;
fayeClient: Faye.Client<RealTimeMessage<UserType, ActivityType>> | null;
browser: boolean;
node: boolean;
nodeOptions?: { httpAgent: http.Agent; httpsAgent: https.Agent };
Expand All @@ -153,9 +165,30 @@ export class StreamClient<
>;
handlers: Record<string, HandlerCallback>;

currentUser?: StreamUser<UserType>;
personalization: Personalization<PersonalizationType>;
collections: Collections<CollectionType>;
currentUser?: StreamUser<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>;
personalization: Personalization<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>;
collections: Collections<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>;
files: StreamFileStore;
images: StreamImageStore;
reactions: StreamReaction<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType>;
Expand Down Expand Up @@ -243,20 +276,38 @@ export class StreamClient<
...(this.nodeOptions || {}),
});

this.personalization = new Personalization<PersonalizationType>(this);
this.personalization = new Personalization<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>(this);

if (this.browser && this.usingApiSecret) {
throw new FeedError(
'You are publicly sharing your App Secret. Do not expose the App Secret in browsers, "native" mobile apps, or other non-trusted environments.',
);
}
this.collections = new Collections<CollectionType>(this, this.getOrCreateToken());
this.files = new StreamFileStore(this, this.getOrCreateToken());
this.images = new StreamImageStore(this, this.getOrCreateToken());
this.reactions = new StreamReaction<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType>(
this,
this.getOrCreateToken(),
);
this.collections = new Collections<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>(this, this.getOrCreateToken());
this.files = new StreamFileStore(this as StreamClient, this.getOrCreateToken());
this.images = new StreamImageStore(this as StreamClient, this.getOrCreateToken());
this.reactions = new StreamReaction<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>(this, this.getOrCreateToken());

// If we are in a node environment and batchOperations/createRedirectUrl is available add the methods to the prototype of StreamClient
if (BatchOperations && !!createRedirectUrl) {
Expand Down Expand Up @@ -436,9 +487,11 @@ export class StreamClient<
*/
feed(
feedSlug: string,
userId?: string | StreamUser<UserType>,
userId?:
| string
| StreamUser<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>,
token?: string,
): StreamFeed<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType> {
): StreamFeed<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType> {
if (userId instanceof StreamUser) userId = userId.id;

if (token === undefined) {
Expand All @@ -449,7 +502,7 @@ export class StreamClient<
}
}

return new StreamFeed<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType>(
return new StreamFeed<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>(
this,
feedSlug,
userId || (this.userId as string),
Expand Down Expand Up @@ -554,10 +607,16 @@ export class StreamClient<
* @private
* @return {Faye.Middleware} Faye authorization middleware
*/
getFayeAuthorization(): Faye.Middleware {
getFayeAuthorization() {
return {
incoming: (message: Faye.Message, callback: Faye.Callback) => callback(message),
outgoing: (message: Faye.Message, callback: Faye.Callback) => {
incoming: (
message: Faye.Message<RealTimeMessage<UserType, ActivityType>>,
callback: Faye.Callback<RealTimeMessage<UserType, ActivityType>>,
) => callback(message),
outgoing: (
message: Faye.Message<RealTimeMessage<UserType, ActivityType>>,
callback: Faye.Callback<RealTimeMessage<UserType, ActivityType>>,
) => {
if (message.subscription && this.subscriptions[message.subscription]) {
const subscription = this.subscriptions[message.subscription];

Expand All @@ -583,7 +642,7 @@ export class StreamClient<
*/
getFayeClient(timeout = 10) {
if (this.fayeClient === null) {
this.fayeClient = new Faye.Client(this.fayeUrl, { timeout });
this.fayeClient = new Faye.Client<RealTimeMessage<UserType, ActivityType>>(this.fayeUrl, { timeout });
const authExtension = this.getFayeAuthorization();
this.fayeClient.addExtension(authExtension);
}
Expand Down Expand Up @@ -807,8 +866,12 @@ export class StreamClient<
return this._getOrCreateToken;
}

user(userId: string): StreamUser<UserType> {
return new StreamUser<UserType>(this, userId, this.getOrCreateToken());
user(userId: string) {
return new StreamUser<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>(
this,
userId,
this.getOrCreateToken(),
);
}

async setUser(data: UserType) {
Expand All @@ -819,7 +882,14 @@ export class StreamClient<
const body = { ...data };
delete body.id;

const user = await (this.currentUser as StreamUser<UserType>).getOrCreate(body);
const user = await (this.currentUser as StreamUser<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>).getOrCreate(body);
this.currentUser = user;
return user;
}
Expand Down
62 changes: 43 additions & 19 deletions src/collections.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StreamClient, APIResponse, UnknownRecord } from './client';
import { StreamClient, APIResponse, UR } from './client';
import { SiteError } from './errors';

type BaseCollection<CollectionType> = {
Expand All @@ -7,30 +7,25 @@ type BaseCollection<CollectionType> = {
id: string;
};

export type CollectionResponse<
CollectionType extends UnknownRecord = UnknownRecord
> = BaseCollection<CollectionType> & {
export type CollectionResponse<CollectionType extends UR = UR> = BaseCollection<CollectionType> & {
created_at: string;
foreign_id: string;
updated_at: string;
};

export type NewCollectionEntry<
CollectionType extends UnknownRecord = UnknownRecord
> = BaseCollection<CollectionType> & {
export type NewCollectionEntry<CollectionType extends UR = UR> = BaseCollection<CollectionType> & {
user_id?: string;
};

export type CollectionAPIResponse<CollectionType extends UnknownRecord = UnknownRecord> = APIResponse &
CollectionResponse<CollectionType>;
export type CollectionAPIResponse<CollectionType extends UR = UR> = APIResponse & CollectionResponse<CollectionType>;

export type SelectCollectionAPIResponse<CollectionType extends UnknownRecord = UnknownRecord> = APIResponse & {
export type SelectCollectionAPIResponse<CollectionType extends UR = UR> = APIResponse & {
response: {
data: CollectionResponse<CollectionType>[];
};
};

export type UpsertCollectionAPIResponse<CollectionType extends UnknownRecord = UnknownRecord> = APIResponse & {
export type UpsertCollectionAPIResponse<CollectionType extends UR = UR> = APIResponse & {
data: {
[key: string]: {
data: CollectionType;
Expand All @@ -39,15 +34,27 @@ export type UpsertCollectionAPIResponse<CollectionType extends UnknownRecord = U
};
};

export class CollectionEntry<CollectionType extends UnknownRecord = UnknownRecord> {
export class CollectionEntry<
UserType extends UR = UR,
ActivityType extends UR = UR,
CollectionType extends UR = UR,
ReactionType extends UR = UR,
ChildReactionType extends UR = UR,
PersonalizationType extends UR = UR
> {
id: string;
collection: string;
store: Collections<CollectionType>; // eslint-disable-line no-use-before-define
store: Collections<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>; // eslint-disable-line no-use-before-define
data: CollectionType | null;
full?: unknown;

// eslint-disable-next-line no-use-before-define
constructor(store: Collections<CollectionType>, collection: string, id: string, data: CollectionType) {
constructor(
// eslint-disable-next-line no-use-before-define
store: Collections<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>,
collection: string,
id: string,
data: CollectionType,
) {
this.collection = collection;
this.store = store;
this.id = id;
Expand Down Expand Up @@ -119,8 +126,15 @@ export class CollectionEntry<CollectionType extends UnknownRecord = UnknownRecor
}
}

export class Collections<CollectionType extends UnknownRecord = UnknownRecord> {
client: StreamClient;
export class Collections<
UserType extends UR = UR,
ActivityType extends UR = UR,
CollectionType extends UR = UR,
ReactionType extends UR = UR,
ChildReactionType extends UR = UR,
PersonalizationType extends UR = UR
> {
client: StreamClient<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>;
token: string;

/**
Expand All @@ -130,7 +144,10 @@ export class Collections<CollectionType extends UnknownRecord = UnknownRecord> {
* @param {StreamCloudClient} client Stream client this collection is constructed from
* @param {string} token JWT token
*/
constructor(client: StreamClient, token: string) {
constructor(
client: StreamClient<UserType, ActivityType, CollectionType, ReactionType, ChildReactionType, PersonalizationType>,
token: string,
) {
this.client = client;
this.token = token;
}
Expand All @@ -141,7 +158,14 @@ export class Collections<CollectionType extends UnknownRecord = UnknownRecord> {
};

entry(collection: string, itemId: string, itemData: CollectionType) {
return new CollectionEntry<CollectionType>(this, collection, itemId, itemData);
return new CollectionEntry<
UserType,
ActivityType,
CollectionType,
ReactionType,
ChildReactionType,
PersonalizationType
>(this, collection, itemId, itemData);
}

/**
Expand Down
14 changes: 7 additions & 7 deletions src/connect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StreamClient, UnknownRecord, ClientOptions } from './client';
import { StreamClient, UR, ClientOptions } from './client';

/**
* Create StreamClient
Expand All @@ -24,12 +24,12 @@ import { StreamClient, UnknownRecord, ClientOptions } from './client';
* "https://thierry:pass@gestream.io/?app=1"
*/
export function connect<
UserType extends UnknownRecord = UnknownRecord,
ActivityType extends UnknownRecord = UnknownRecord,
CollectionType extends UnknownRecord = UnknownRecord,
ReactionType extends UnknownRecord = UnknownRecord,
ChildReactionType extends UnknownRecord = UnknownRecord,
PersonalizationType extends UnknownRecord = UnknownRecord
UserType extends UR = UR,
ActivityType extends UR = UR,
CollectionType extends UR = UR,
ReactionType extends UR = UR,
ChildReactionType extends UR = UR,
PersonalizationType extends UR = UR
>(apiKey: string, apiSecret: string | null, appId?: string, options?: ClientOptions) {
if (typeof process !== 'undefined' && process.env?.STREAM_URL && !apiKey) {
const parts = /https:\/\/(\w+):(\w+)@([\w-]*).*\?app_id=(\d+)/.exec(process.env.STREAM_URL) || [];
Expand Down
Loading

0 comments on commit 869ffa8

Please sign in to comment.