Refactor project for typescript support

Update and move tsconfig.json from client/ to the root folder.
Update docker-compose.yml and .dockerignore.
Update client/webpack.config.js for better typescript support.

Change-Id: I3b0cc88f7c828dc5bfa5ac129352bf3d40189af3
diff --git a/app.ts b/app.ts
new file mode 100644
index 0000000..eea1786
--- /dev/null
+++ b/app.ts
@@ -0,0 +1,345 @@
+'use strict'
+
+import dotenv from 'dotenv'
+const env = dotenv.config()
+
+import { promises as fs } from 'fs'
+import http from 'http'
+import express from 'express'
+import session from 'express-session'
+import cookieParser  from'cookie-parser'
+import { Server } from'socket.io'
+import path from 'path'
+import passport from 'passport'
+import { Strategy as LocalStrategy } from 'passport-local'
+//import { createRequire } from 'module';
+//const require = createRequire(import.meta.url);
+
+//const redis = require('redis-url').connect()
+//const RedisStore = require('connect-redis')(session)
+/*const passportSocketIo = require('passport.socketio')*/
+
+import indexRouter from './routes/index.js'
+
+import cors from 'cors'
+
+import JamiRestApi from './routes/jami.js'
+import JamiDaemon from './JamiDaemon.js'
+// import { sentrySetUp } from './sentry.js'
+
+const configPath = 'jamiServerConfig.json'
+
+//const sessionStore = new RedisStore({ client: redis })
+const sessionStore = new session.MemoryStore()
+
+const loadConfig = async (filePath) => {
+    const config = {users: {}, authMethods: []}
+    try {
+        return Object.assign(config, JSON.parse((await fs.readFile(filePath)).toString()))
+    } catch(e) {
+        console.log(e)
+        return config
+    }
+}
+
+const saveConfig = (filePath, config) => {
+    return fs.writeFile(filePath, JSON.stringify(config))
+}
+
+/*
+Share sessions between Passport.js and Socket.io
+*/
+
+function logSuccess() {
+    console.log('passportSocketIo authorized user with Success 😁')
+}
+
+function logFail() {
+    console.log('passportSocketIo failed to authorized user 👺')
+}
+
+/*
+
+tempAccounts holds users accounts while tempting to authenticate them on Jams.
+connectedUsers  holds users accounts after they got authenticated by Jams.
+
+Users should be removed from connectedUsers when receiving a disconnect
+web socket call
+
+*/
+const tempAccounts = {}
+const connectedUsers = {}
+
+const createServer = async (appConfig) => {
+    const node_env = process.env.NODE_ENV || 'development'
+    const app = express()
+    console.log(`Loading server for ${node_env} with config:`)
+    console.log(appConfig)
+
+    var corsOptions = {
+        origin: 'http://127.0.0.1:3000'
+    }
+
+    if (node_env === 'development') {
+        const webpack = await import('webpack')
+        const webpackDev = await import('webpack-dev-middleware')
+        const webpackHot = await import ('webpack-hot-middleware')
+        const {default: webpackConfig} = await import ('jami-web-client/webpack.config.js') as any
+
+        const compiler = webpack.default(webpackConfig)
+        app.use(webpackDev.default(compiler, {
+            publicPath: webpackConfig.output.publicPath
+        }))
+        app.use(webpackHot.default(compiler))
+    }
+
+    /*
+        Configuation for Passeport Js
+    */
+    app.disable('x-powered-by')
+
+    const sessionMiddleware = session({
+        store: sessionStore,
+        resave: false,
+        saveUninitialized: true,
+        cookie: {
+            secure: false,//!development,
+            maxAge: 2419200000
+        },
+        secret: process.env.SECRET_KEY_BASE
+    })
+
+    app.use(sessionMiddleware)
+    app.use(passport.initialize())
+    app.use(passport.session())
+    // app.use(app.router)
+    app.use(cors(corsOptions))
+
+    const jami = new JamiDaemon((account, conversation, message) => {
+        console.log('JamiDaemon onMessage')
+
+        if (conversation.listeners) {
+            Object.values(conversation.listeners).forEach((listener: any) => {
+                listener.socket.emit('newMessage', message)
+            })
+        }
+    })
+    const apiRouter = new JamiRestApi(jami).getRouter()
+
+    /*
+    io.use(passportSocketIo.authorize({
+        key: 'connect.sid',
+        secret: process.env.SECRET_KEY_BASE,
+        store: sessionStore,
+        passport: passport,
+        cookieParser: cookieParser,
+        //success: logSuccess(),
+        // fail: logFail(),
+    }))
+    */
+
+    const isSetupComplete = () => {
+        return 'admin' in appConfig.users
+    }
+
+    const accountFilter = filter => {
+        if (typeof filter === 'string') {
+            if (filter === '*')
+                return undefined
+            else
+                return account => account.getId() === filter
+        } else if (Array.isArray(filter)) {
+            return account => filter.includes(account.getId())
+        } else {
+            throw new Error('Invalid account filter string')
+        }
+    }
+
+    const user = (id, config) => {
+        return {
+            id,
+            config,
+            username: config.username || id,
+            accountFilter: accountFilter(config.accounts)
+        }
+    }
+
+    passport.serializeUser((user, done) => {
+        connectedUsers[user.id] = user.config
+        console.log('=============================SerializeUser called ' + user.id)
+        console.log(user)
+        done(null, user.id)
+    })
+
+    const deserializeUser = (id, done) => {
+        console.log('=============================DeserializeUser called on: ' + id)
+        const userConfig = connectedUsers[id]
+        console.log(userConfig)
+        if (userConfig) {
+            done(null, user(id, userConfig))
+        } else
+            done(404, null)
+    }
+    passport.deserializeUser(deserializeUser)
+
+    const jamsStrategy = new LocalStrategy(
+        async (username, password, done) => {
+            const accountId = await jami.addAccount({
+                'managerUri': 'https://jams.savoirfairelinux.com',
+                'managerUsername': username,
+                'archivePassword': password
+            })
+            const id = `jams_${username}`
+            const userConfig = { username, type: 'jams', accounts: accountId }
+            const newUser = user(id, userConfig)
+            console.log('AccountId: ' + accountId)
+            tempAccounts[accountId] = { done, newUser }
+
+        }
+    )
+    jamsStrategy.name = 'jams'
+
+    const localStrategy = new LocalStrategy(
+        (username, password, done) => {
+            console.log('localStrategy: ' + username + ' ' + password)
+
+            const id = username
+            const userConfig = appConfig.users[username]
+            if (!userConfig) {
+                return done(null, false, { message: 'Incorrect username.' })
+            }
+            if (userConfig.password !== password) {
+                return done(null, false, { message: 'Incorrect password.' })
+            }
+            userConfig.type = 'local'
+
+            done(null, user(id, userConfig))
+        }
+    )
+
+    passport.use(jamsStrategy)
+    passport.use(localStrategy)
+
+    const secured = (req, res, next) => {
+        if (req.user) {
+            return next()
+        }
+        res.status(401).end()
+    }
+    const securedRedirect = (req, res, next) => {
+        if (req.user && req.user.accountId) {
+            return next()
+        }
+        req.session.returnTo = req.originalUrl
+        res.redirect('/login')
+    }
+
+    app.use(express.json())
+    app.post('/setup', (req, res) => {
+        if (isSetupComplete()) {
+            return res.status(404).end()
+        }
+        if (!req.body.password) {
+            return res.status(400).end()
+        }
+        console.log(req.body)
+        appConfig.users.admin = {
+            'accounts': '*',
+            password: req.body.password
+        }
+        res.status(200).end()
+        saveConfig(configPath, appConfig)
+    })
+    app.post('/auth/jams', passport.authenticate('jams'), (req, res) => {
+        res.json({ loggedin: true })
+    })
+    app.post('/auth/local', passport.authenticate('local'), (req, res) => {
+        res.json({ loggedin: true, user: req.user.id })
+    })
+
+    const getState = req => {
+        if (req.user) {
+            return { loggedin: true, username: req.user.username, type: req.user.type }
+        } else if (isSetupComplete()) {
+            return {}
+        } else {
+            return { setupComplete: false }
+        }
+    }
+
+    // sentrySetUp(app);
+
+    app.get('/auth', (req, res) => {
+        const state = getState(req)
+        if (req.user) {
+            res.json(state)
+        } else {
+            res.status(401).json(state)
+        }
+    })
+
+    app.use('/api', secured, apiRouter)
+
+    app.use('/', indexRouter)
+
+    /* GET React App */
+
+    const cwd = process.cwd()
+    app.use(express.static(path.join(cwd,  'client/dist')));
+
+    app.use((req, res) => {
+        res.render(path.join(cwd, 'client/dist/index.ejs'), {
+            initdata: JSON.stringify(getState(req))
+        })
+    })
+
+
+    // @ts-ignore TODO: Fix the typescript error
+    const server = http.Server(app)
+
+    const io = new Server(server, { cors: corsOptions })
+    const wrap = middleware => (socket, next) => middleware(socket.request, {}, next)
+    io.use(wrap(sessionMiddleware))
+    io.use(wrap(passport.initialize()))
+    io.use(wrap(passport.session()))
+    io.use((socket, next) => {
+        if ((socket.request as any).user) {
+            next()
+        } else {
+            next(new Error('unauthorized'))
+        }
+    })
+    io.on('connect', (socket) => {
+        console.log(`new connection ${socket.id}`)
+        const session = (socket.request as any).session
+        console.log(`saving sid ${socket.id} in session ${session.id}`)
+        session.socketId = socket.id
+        session.save()
+
+        socket.on('conversation', (data) => {
+            console.log('io conversation')
+            console.log(data)
+            if (session.conversation) {
+                console.log(`disconnect from old conversation ${session.conversation.conversationId}`)
+                const conversation = jami.getConversation(session.conversation.accountId, session.conversation.conversationId)
+                delete conversation.listeners[socket.id]
+            }
+            session.conversation = { accountId: data.accountId, conversationId: data.conversationId }
+            const conversation = jami.getConversation(data.accountId, data.conversationId)
+            if (!conversation.listeners)
+                conversation.listeners = {}
+            conversation.listeners[socket.id] = {
+                socket, session
+            }
+            session.save()
+        })
+    })
+
+    return server
+}
+
+loadConfig(configPath)
+    .then(createServer)
+    .then(server => {
+        server.listen(3000)
+    })