Skip to content
Open
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
336 changes: 38 additions & 298 deletions functions/app.js

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions functions/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const js = require("@eslint/js");
const globals = require("globals");

module.exports = [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2022,
sourceType: "commonjs",
globals: {
...globals.node,
},
},
rules: {
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"no-undef": "error",
},
},
];
35 changes: 11 additions & 24 deletions functions/index.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,36 @@
'use strict';

const functions = require('firebase-functions');
const functions = require('firebase-functions/v1');
const admin = require("firebase-admin");
admin.initializeApp();

const app = require('./app');
const oauth = require('./oauth2');

const express = require('express')();
const bodyParser = require('body-parser');
express.use(require("morgan")('dev'));
express.use(bodyParser.json());
express.use(bodyParser.urlencoded({extended: true}));
express.set('trust proxy', 1);
express.use(require('cookie-parser')());
express.set("view engine", "ejs");
express.engine("ejs", require("ejs").__express);
if (admin.apps.length === 0) {
admin.initializeApp();
}

express.all('/', app.root);
express.all('/request-sync', app.requestsync);
express.all('/report-state', app.reportstate);
express.all('/token', oauth.token);
express.all('/auth', oauth.auth);
const server = require('./server');

exports.authAction = functions.auth.user().onCreate((userRecord, context) => {
exports.authAction = functions.auth.user().onCreate((userRecord, _context) => {
const uid = userRecord.uid;
const email = userRecord.email
const email = userRecord.email;
console.log('user added:', uid);
return admin.firestore().collection('users').doc(uid).set({
id: uid,
name: email,
createdAt: (new Date()).toJSON()
});
});
exports.delAccount = functions.auth.user().onDelete((userRecord, context) => {

exports.delAccount = functions.auth.user().onDelete((userRecord, _context) => {
const uid = userRecord.uid;
return admin.firestore().collection('users').doc(uid).delete();
});

function wrapCloudFunctionHandler(handler) {
return (req, res) => {
if(!req.url || !req.path) req.url = '/' + (req.url || '');
if (!req.url || !req.path) req.url = '/' + (req.url || '');
delete req.headers['x-forwarded-proto'];
handler(req, res);
};
}

exports.ha = functions.https.onRequest(wrapCloudFunctionHandler(express));
exports.ha = functions.https.onRequest(wrapCloudFunctionHandler(server));
119 changes: 119 additions & 0 deletions functions/intents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
'use strict';

const Model = require('./models');
const { valuniq, flatten, showError } = require('./utils');

const payload_data = (deviceId, deviceNickname, deviceData) => {
const deviceType = deviceData.type;
const deviceManufacturer = deviceData.manufacturer;
const deviceModel = deviceData.model;
const devicewillReportState = deviceData.willReportState;
const deviceTraits = deviceData.traits;
const deviceAttributes = deviceData.attributes;
let deviceName = deviceData.name;
if (!deviceName) {
if (deviceManufacturer && deviceModel) {
deviceName = String(`${deviceManufacturer} ${deviceModel}`);
} else {
deviceName = deviceData.id;
}
}
const device_payload = {
id: deviceId,
type: deviceType,
name: {
defaultNames: [deviceName],
name: deviceName,
nicknames: [deviceNickname]
},
deviceInfo: {},
traits: [],
willReportState: devicewillReportState
};
if (deviceManufacturer) {
device_payload['deviceInfo']['manufacturer'] = deviceManufacturer;
}
if (deviceModel) {
device_payload['deviceInfo']['model'] = deviceModel;
}
if (deviceTraits) {
device_payload['traits'] = deviceTraits;
}
if (deviceAttributes) {
device_payload['attributes'] = deviceAttributes;
}
return device_payload;
};

const sync = async (req, res, params = {}) => {
try {
const deviceProps = { requestId: req.body.requestId, payload: { agentUserId: params.uid, devices: [] } };
const deviceData = await Promise.all([
Model.getGroupDevicesByUserId(params.uid),
Model.getUserDevices(params.uid)
]);

valuniq(flatten(deviceData)).forEach((data) => {
deviceProps['payload']['devices'].push(payload_data(data.id, data.name, data.data));
});

console.log('sync:', JSON.stringify(deviceProps));
res.status(200).json(deviceProps);
} catch (error) {
showError(res, error.message || error);
}
};

const query = async (req, res, _params = {}) => {
try {
const deviceStates = { requestId: req.body.requestId, payload: { devices: {} } };
const reqStates = req.body.inputs[0].payload.devices;

const states = await Promise.all(reqStates.map(reqDevice => Model.getStates(reqDevice.id)));

deviceStates['payload']['devices'] = states.reduce((o, c) => Object.assign(o, { [c.id]: c.data }), {});
console.log('query:', JSON.stringify(deviceStates));
res.status(200).json(deviceStates);
} catch (error) {
showError(res, error.message || error);
}
};

const execute = async (req, res, _params = {}) => {
try {
const respCommands = [];
const reqCommands = req.body.inputs[0].payload.commands;
const resPromises = [];

reqCommands.forEach((curCommand) => {
curCommand.devices.forEach((curDevice) => {
let deviceId = String(curDevice.id);
let commandsData = [];
curCommand.execution.forEach((curExec) => {
commandsData.push({ command: curExec.command, params: curExec.params });
});
resPromises.push(Model.setCommands(deviceId, commandsData));
respCommands.push({ ids: [deviceId], status: "SUCCESS" });
});
});

const resBody = {
requestId: req.body.requestId,
payload: {
commands: respCommands
}
};

await Promise.all(resPromises);
console.log('execute:', JSON.stringify(resBody));
res.status(200).json(resBody);
} catch (error) {
showError(res, error.message || error);
}
};

module.exports = {
sync,
query,
execute
};
141 changes: 141 additions & 0 deletions functions/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';

const admin = require("firebase-admin");

const getFromFirestore = (collection, doc) => {
return admin.firestore().collection(collection).doc(doc);
};

const getFromDatabase = (refkey, pkey) => {
return admin.database().ref(refkey + '/' + pkey).once('value');
};

const Model = {};

const getPersonalDevices = async (personal, personalId) => {
let personalId_key = undefined;
switch (personal) {
case "group_devices":
personalId_key = 'groupId';
break;
case "user_devices":
personalId_key = 'userId';
break;
default:
throw new SyntaxError('personal required');
}

const querysnap = await admin.firestore().collection(personal).where(personalId_key, '==', personalId).get();

const devicePromises = querysnap.docs.map(async (docsnap) => {
const docdata = docsnap.data();
if (!docdata.deviceId && !docdata.deviceReference) {
throw new SyntaxError('require deviceId or deviceReference in group_devices.');
}

let data;
if (docdata.deviceReference) {
const devsnap = await docdata.deviceReference.get();
data = devsnap.exists ? devsnap.data() : null;
} else {
const devobj = await Model.getDevice(docdata.deviceId);
data = devobj.data || null;
}

return {
id: docsnap.id,
name: docdata.name,
data: data
};
});

return Promise.all(devicePromises);
};

Model.getGroupDevices = (groupId) => {
return getPersonalDevices('group_devices', groupId);
};

Model.getUserDevices = (userId) => {
return getPersonalDevices('user_devices', userId);
};

Model.getDevice = async (deviceId) => {
const docsnap = await getFromFirestore('devices', deviceId).get();
if (docsnap && docsnap.exists) {
return {
id: docsnap.id,
data: docsnap.data()
};
}
return {};
};

Model.getRemote = async (remoteId) => {
const docsnap = await getFromFirestore('remotes', remoteId).get();
if (docsnap && docsnap.exists) {
return {
id: docsnap.id,
data: docsnap.data()
};
}
return {};
};

Model.getUser = async (userId) => {
const docsnap = await getFromFirestore('users', userId).get();
if (docsnap && docsnap.exists) {
return {
id: docsnap.id,
data: docsnap.data()
};
}
return {};
};

Model.getGroup = async (groupId) => {
const docsnap = await getFromFirestore('groups', groupId).get();
if (docsnap && docsnap.exists) {
return {
id: docsnap.id,
data: docsnap.data()
};
}
return {};
};

Model.getCommands = async (indivDeviceId) => {
const snap = await getFromDatabase('commands', indivDeviceId);
return {
id: indivDeviceId,
data: snap.val()
};
};

Model.setCommands = (indivDeviceId, commandData) => {
return admin.database().ref('commands/' + indivDeviceId).set(commandData);
};

Model.getStates = async (indivDeviceId) => {
const snap = await getFromDatabase('states', indivDeviceId);
return {
id: indivDeviceId,
data: snap.val()
};
};

Model.setStates = (indivDeviceId, statesData) => {
return admin.database().ref('states/' + indivDeviceId).update(statesData);
};

Model.getGroupDevicesByUserId = async (userId) => {
const querysnap = await admin.firestore().collection('groups').select(userId).get();
const groupDevicePromises = [];
querysnap.forEach((docsnap) => {
if (docsnap && docsnap.exists)
groupDevicePromises.push(Model.getGroupDevices(docsnap.id));
});
return Promise.all(groupDevicePromises);
};

module.exports = Model;
Loading
Loading