Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support observeProperty in binding-firestore #808

Merged
merged 3 commits into from
Aug 9, 2022
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
4 changes: 2 additions & 2 deletions packages/binding-firestore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ The WoT operations that can be implemented for Client as follows.
| readMultipleProperties | - |
| writeProperty | ✓ |
| writeMultipleProperties | - |
| observeProperty | - |
| unobserveProperty | - |
| observeProperty | |
| unobserveProperty | |
| invokeAction | ✓ |
| emitEvent | N/A |
| subscribeEvent | ✓ |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class FirestoreClientFactory implements ProtocolClientFactory {
}

public getClient(): ProtocolClient {
console.warn(`[warn] firebaseClientFactory creating client`);
console.warn(`[binding-firestore] firebaseClientFactory creating client`);
if (this.firestoreClient === null) {
this.firestoreClient = new FirestoreClient(this.config);
}
Expand Down
24 changes: 13 additions & 11 deletions packages/binding-firestore/src/firestore-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { ProtocolClient, Content } from "@node-wot/core";
import { FirestoreForm, BindingFirestoreConfig } from "./firestore";
import { v4 as uuidv4 } from "uuid";

import "firebase/auth";
import { Firestore } from "firebase/firestore";
import "firebase/compat/auth";
import Firebase from "firebase/compat/app";
import {
initFirestore,
writeDataToFirestore,
Expand All @@ -32,9 +32,11 @@ import {
import * as TD from "@node-wot/td-tools";
import { Subscription } from "rxjs/Subscription";

type Firestore = Firebase.firestore.Firestore;

export default class FirestoreClient implements ProtocolClient {
private firestore: Firestore = null;
private firestoreObservers = {};
private firestoreObservers: { [key: string]: () => void } = {};
private fbConfig: BindingFirestoreConfig = null;

constructor(config: BindingFirestoreConfig = null) {
Expand Down Expand Up @@ -109,16 +111,16 @@ export default class FirestoreClient implements ProtocolClient {
this.firestoreObservers,
propertyReadResultTopic,
(err, content, resId) => {
console.debug("[debug] return read property result");
console.debug(`[debug] reqId ${reqId}, resId ${resId}`);
console.debug("[binding-firestore] return read property result");
console.debug(`[binding-firestore] reqId ${reqId}, resId ${resId}`);
if (reqId !== resId) {
// Ignored because reqId and resId do not match
return;
}
unsubscribeToFirestore(this.firestoreObservers, propertyReadResultTopic);
clearTimeout(timeoutId);
if (err) {
console.error("[error] failed to get reading property result:", err);
console.error("[binding-firestore] failed to get reading property result:", err);
reject(err);
} else {
resolve(content);
Expand Down Expand Up @@ -168,16 +170,16 @@ export default class FirestoreClient implements ProtocolClient {
let timeoutId: NodeJS.Timeout;
const retContent: Content = await new Promise((resolve, reject) => {
subscribeToFirestore(this.firestore, this.firestoreObservers, actionResultTopic, (err, content, resId) => {
console.debug("[debug] return action and unsubscribe");
console.debug(`[debug] reqId ${reqId}, resId ${resId}`);
console.debug("[binding-firestore] return action and unsubscribe");
console.debug(`[binding-firestore] reqId ${reqId}, resId ${resId}`);
if (reqId !== resId) {
// Ignored because reqId and resId do not match
return;
}
unsubscribeToFirestore(this.firestoreObservers, actionResultTopic);
clearTimeout(timeoutId);
if (err) {
console.error("[error] failed to get action result:", err);
console.error("[binding-firestore] failed to get action result:", err);
reject(err);
} else {
resolve(content);
Expand Down Expand Up @@ -232,7 +234,7 @@ export default class FirestoreClient implements ProtocolClient {
pointerInfo.topic,
(err: Error, content) => {
if (err) {
console.error("[error] failed to subscribe resource: ", err);
console.error("[binding-firestore] failed to subscribe resource: ", err);
error(err);
} else {
next(content);
Expand All @@ -246,7 +248,7 @@ export default class FirestoreClient implements ProtocolClient {
);
})
.catch((err) => {
console.error("[error] failed to init firestore: ", err);
console.error("[binding-firestore] failed to init firestore: ", err);
error(err);
});
});
Expand Down
104 changes: 64 additions & 40 deletions packages/binding-firestore/src/firestore-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ import "firebase/compat/firestore";
import { Readable } from "stream";
import { BindingFirestoreConfig } from "./firestore";

let firebase: any;
let firebase: typeof Firebase;
if (Firebase.apps) {
// for NodeJS
firebase = Firebase;
} else {
// for Web browser (We'll deal with it later.)
firebase = window.firebase;
firebase = window.firebase as unknown as typeof Firebase;
}

type Firestore = Firebase.firestore.Firestore;

/**
* initialize firestore.
*/
export const initFirestore = async (fbConfig: BindingFirestoreConfig, fstore: any): Promise<any> => {
export const initFirestore = async (fbConfig: BindingFirestoreConfig, fstore: Firestore): Promise<Firestore> => {
if (fstore != null) {
return fstore;
}
Expand All @@ -42,7 +44,7 @@ export const initFirestore = async (fbConfig: BindingFirestoreConfig, fstore: an
}
// Sign In
const currentUser = await new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user: any) => {
firebase.auth().onAuthStateChanged((user) => {
resolve(user);
});
});
Expand All @@ -62,19 +64,19 @@ export const initFirestore = async (fbConfig: BindingFirestoreConfig, fstore: an
reject(error);
});
});
return firestore;
return firestore as Firestore;
} else {
return firebase.firestore();
}
};

export const writeDataToFirestore = async (
firestore: any,
firestore: Firebase.firestore.Firestore,
topic: string,
content: Content,
reqId: string = null
): Promise<void> => {
console.debug("[debug] writeDataToFirestore topic:", topic, " value:", content, reqId);
console.debug("[binding-firestore] writeDataToFirestore topic:", topic, reqId);
const ref = firestore.collection("things").doc(encodeURIComponent(topic));
const data = { updatedTime: Date.now(), reqId, content: "" };
if (content && content.body) {
Expand All @@ -86,27 +88,38 @@ export const writeDataToFirestore = async (
data.content = JSON.stringify(content);
}
} else {
const contentForWrite: any = { type: null, body: null };
const contentForWrite: { type: string | null; body: { type: string | null; data: [] | null } | string | null } =
{
type: null,
body: null,
};
data.content = JSON.stringify(contentForWrite);
}
console.debug("[debug] writeDataToFirestore topic:", topic, " data:", data, reqId);
console.debug("[binding-firestore] writeDataToFirestore topic:", topic, " data:", data, reqId);
try {
return await ref.set(data);
} catch (err) {
console.error("[error] failed to write data to firestore: ", err, " topic: ", topic, " data: ", data);
console.error(
"[binding-firestore] failed to write data to firestore: ",
err,
" topic: ",
topic,
" data: ",
data
);
throw err;
}
};

export const readDataFromFirestore = async (firestore: any, topic: string): Promise<Content> => {
console.debug("[debug] readDataFromFirestore topic:", topic);
export const readDataFromFirestore = async (firestore: Firestore, topic: string): Promise<Content> => {
console.debug("[binding-firestore] readDataFromFirestore topic:", topic);
const ref = firestore.collection("things").doc(encodeURIComponent(topic));
try {
const doc = await ref.get();
if (doc.exists) {
const data = doc.data();
let content: Content = null;
console.debug("[debug] readDataToFirestore gotten data:", data);
console.debug("[binding-firestore] readDataToFirestore gotten data:", data);
if (data && data.content) {
// XXX TODO change the way content is reported
const obj = JSON.parse(data.content);
Expand All @@ -123,27 +136,27 @@ export const readDataFromFirestore = async (firestore: any, topic: string): Prom
}
return content;
} else {
console.debug("[debug] read data from firestore but no contents topic:", topic);
console.debug("[binding-firestore] read data from firestore but no contents topic:", topic);
throw new Error("no contents");
}
} catch (err) {
console.error("[error] failed read data from firestore: ", err, " topic: ", topic);
console.error("[binding-firestore] failed read data from firestore: ", err, " topic: ", topic);
throw err;
}
};

export const subscribeToFirestore = async (
firestore: any,
firestoreObservers: any,
firestore: Firestore,
firestoreObservers: { [key: string]: () => void },
topic: string,
callback: (err: Error | string | null, content?: Content, reqId?: string) => void
): Promise<void> => {
console.debug("[debug] subscribeToFirestore topic:", topic);
console.debug("[binding-firestore] subscribeToFirestore topic:", topic);
let firstFlg = true;
const ref = firestore.collection("things").doc(encodeURIComponent(topic));
let reqId: string;
const observer = ref.onSnapshot(
async (doc: any) => {
async (doc) => {
const data = await doc.data();
// If reqId is included and Topic contains actionResults,
// return the value regardless of whether it is the first acquisition because it is a return value.
Expand All @@ -160,7 +173,7 @@ export const subscribeToFirestore = async (
}
if (firstFlg) {
firstFlg = false;
console.debug("[debug] ignore because first calling: " + topic);
console.debug("[binding-firestore] ignore because first calling: " + topic);
return;
}

Expand All @@ -180,34 +193,38 @@ export const subscribeToFirestore = async (
callback(null, content, reqId);
},
(err: Error) => {
console.error("[error] failed to subscribe data from firestore: ", err, " topic: ", topic);
console.error("[binding-firestore] failed to subscribe data from firestore: ", err, " topic: ", topic);
callback(err, null, reqId);
}
);
firestoreObservers[topic] = observer;
};

export const unsubscribeToFirestore = (firestoreObservers: any, topic: string): void => {
console.debug(" unsubscribeToFirestore topic:", topic);
export const unsubscribeToFirestore = (firestoreObservers: { [key: string]: () => void }, topic: string): void => {
console.debug("[binding-firestore] unsubscribeToFirestore topic:", topic);
const observer = firestoreObservers[topic];
if (observer) {
observer();
}
};

export const removeDataFromFirestore = async (firestore: any, topic: string): Promise<void> => {
console.debug("[debug] removeDataFromFirestore topic: ", topic);
export const removeDataFromFirestore = async (firestore: Firestore, topic: string): Promise<void> => {
console.debug("[binding-firestore] removeDataFromFirestore topic: ", topic);
const ref = firestore.collection("things").doc(encodeURIComponent(topic));
try {
await ref.delete();
} catch (err) {
console.error("error removing topic: ", topic, "error: ", err);
console.error("[binding-firestore] error removing topic: ", topic, "error: ", err);
throw err;
}
};

export const writeMetaDataToFirestore = async (firestore: any, hostName: string, content: unknown): Promise<any> => {
console.debug("[debug] writeMetaDataToFirestore hostName: ", hostName, " value: ", content);
export const writeMetaDataToFirestore = async (
firestore: Firestore,
hostName: string,
content: unknown
): Promise<void> => {
console.debug("[binding-firestore] writeMetaDataToFirestore hostName: ", hostName, " value: ", content);
const data = { updatedTime: Date.now(), content: "" };
try {
const ref = firestore.collection("hostsMetaData").doc(hostName);
Expand All @@ -217,40 +234,47 @@ export const writeMetaDataToFirestore = async (firestore: any, hostName: string,
const value = await ref.set(data);
return value;
} catch (err) {
console.error("[error] failed to write meta data: ", err, " data: ", data, " hostName: ", hostName);
console.error("[binding-firestore] failed to write meta data: ", err, " data: ", data, " hostName: ", hostName);
throw err;
}
};

export const readMetaDataFromFirestore = async (firestore: any, hostName: string): Promise<any> => {
console.debug("[debug] readMetaDataFromFirestore hostName:", hostName);
export const readMetaDataFromFirestore = async (
firestore: Firestore,
hostName: string
): Promise<{ hostName: string; things: string[] } | undefined> => {
console.debug("[binding-firestore] readMetaDataFromFirestore hostName:", hostName);
try {
const ref = firestore.collection("hostsMetaData").doc(hostName);
const doc = ref.get();
const doc = await ref.get();
if (doc.exists) {
const data = doc.data();
const content = JSON.parse(data);
return content.body;
if (data?.content) {
const content = JSON.parse(data.content);
return content.body;
} else {
return null;
}
} else {
console.debug("[debug] read meta data from firestore but no contents");
console.debug("[binding-firestore] read meta data from firestore but no contents");
throw new Error("no contents");
}
} catch (err) {
console.error("[error] failed to read meta data: ", err, " hostName: ", hostName);
console.error("[binding-firestore] failed to read meta data: ", err, " hostName: ", hostName);
throw err;
}
};

// Remove the MetaData corresponding to the hostname from Firestore.
export const removeMetaDataFromFirestore = async (firestore: any, hostName: string): Promise<void> => {
console.debug("[debug] removeMetaDataFromFirestore hostName: ", hostName);
export const removeMetaDataFromFirestore = async (firestore: Firestore, hostName: string): Promise<void> => {
console.debug("[binding-firestore] removeMetaDataFromFirestore hostName: ", hostName);
try {
const ref = firestore.collection("hostsMetaData").doc(hostName);
await ref.delete();
console.log("removed hostName: ", hostName);
console.debug("[binding-firestore] removed hostName: ", hostName);
return;
} catch (err) {
console.error("error removing hostName: ", hostName, "error: ", err);
console.error("[binding-firestore] error removing hostName: ", hostName, "error: ", err);
throw err;
}
};
Loading