change timing to create room

peertube
Namekuji 2023-01-26 17:31:53 -05:00
rodzic b21aa42aaa
commit 1a6b838c9c
11 zmienionych plików z 108 dodań i 79 usunięć

Wyświetl plik

@ -20,7 +20,7 @@ webhook:
room: room:
auto_create: false auto_create: false
empty_timeout: 3600 empty_timeout: 30
max_participants: 0 max_participants: 0
max_metadata_size: 0 max_metadata_size: 0

Wyświetl plik

@ -28,6 +28,8 @@ LIVEKIT_API_SECRET=secret
LIVEKIT_HOST=livekit.example.com LIVEKIT_HOST=livekit.example.com
# This value will be returned by Audon backend to browsers. Set the same domain as LIVEKIT_HOST if you are not sure. # This value will be returned by Audon backend to browsers. Set the same domain as LIVEKIT_HOST if you are not sure.
LIVEKIT_LOCAL_DOMAIN=livekit.example.com LIVEKIT_LOCAL_DOMAIN=livekit.example.com
# If this period (seconds) passes, the new room will be automatically closed.
LIVEKIT_EMPTY_ROOM_TIMEOUT=300
### Bot Settings ### ### Bot Settings ###
# Leave the following fields empty to disable the notification bot. # Leave the following fields empty to disable the notification bot.

Wyświetl plik

@ -26,8 +26,8 @@ comingFuture: "Coming with future update!"
processing: "Processing now...<br />Keep this window open!" processing: "Processing now...<br />Keep this window open!"
lostWarning: "Unsaved data will be lost if you leave the page, are you sure?" lostWarning: "Unsaved data will be lost if you leave the page, are you sure?"
staticLink: staticLink:
title: "Your Static Link" title: "Your Audon Link"
hint: "Other can join to your room via this link while you're hosting." hint: "Other can join to your room via this personal link while you're hosting."
form: form:
title: "Title" title: "Title"
titleRequired: "Room title required" titleRequired: "Room title required"
@ -48,6 +48,7 @@ shareRoomMessage: "Join my Audon room!\n{link}\n\nTitle: {title}"
roomReady: roomReady:
header: "Your room is ready!" header: "Your room is ready!"
message: "Your room \"{title}\" is now ready. Share the following URL with other participants." message: "Your room \"{title}\" is now ready. Share the following URL with other participants."
timeout: "The room will be closed automatically if no one joins within {minutes} minutes."
errors: errors:
offline: "This user is not hosting now." offline: "This user is not hosting now."
invalidAddress: "Invalid address" invalidAddress: "Invalid address"

Wyświetl plik

@ -26,8 +26,8 @@ comingFuture: "今後のアップデートで追加予定"
processing: "処理中です。<br />画面を閉じないでください。" processing: "処理中です。<br />画面を閉じないでください。"
lostWarning: "この画面を閉じると保存前の内容が失われます。構いませんか?" lostWarning: "この画面を閉じると保存前の内容が失われます。構いませんか?"
staticLink: staticLink:
title: "固定リンク" title: "Audon リンク"
hint: "プロフィール欄などに固定しておくと、あなたが部屋をホストしたとき他の人はこのリンクを使って参加できます。" hint: "あなたが部屋をホストしたとき、他の人はこの固定 URL からでも参加できます。"
form: form:
title: "タイトル" title: "タイトル"
titleRequired: "部屋の名前を入力してください" titleRequired: "部屋の名前を入力してください"
@ -48,6 +48,7 @@ shareRoomMessage: "Audon で部屋を作りました!\n参加用リンク {
roomReady: roomReady:
header: "お部屋の用意ができました!" header: "お部屋の用意ができました!"
message: "{title} を作りました。参加者に以下の URL を共有してください。" message: "{title} を作りました。参加者に以下の URL を共有してください。"
timeout: "{minutes} 分以内に誰も入室しないと部屋が閉じますのでご注意ください。"
errors: errors:
offline: "このユーザーは現在ホスト中ではありません。" offline: "このユーザーは現在ホスト中ではありません。"
invalidAddress: "アドレスが有効ではありません" invalidAddress: "アドレスが有効ではありません"

Wyświetl plik

@ -104,11 +104,13 @@ export default {
if (!donURL) return ""; if (!donURL) return "";
const url = new URL(donURL); const url = new URL(donURL);
const texts = [ const texts = [
this.$t("shareRoomMessage", { link: this.roomURL, title: this.title }), this.$t("shareRoomMessage", {
link: this.donStore.myStaticLink,
title: this.title,
}),
]; ];
if (this.description) if (this.description)
texts.push(truncate("\n" + this.description, { length: 200 })); texts.push(truncate("\n" + this.description, { length: 200 }));
texts.push("\n#Audon");
return encodeURI(`${url.origin}/share?text=${texts.join("\n")}`); return encodeURI(`${url.origin}/share?text=${texts.join("\n")}`);
}, },
}, },
@ -209,7 +211,7 @@ export default {
{{ $t("roomReady.message", { title }) }} {{ $t("roomReady.message", { title }) }}
</div> </div>
<div class="my-3"> <div class="my-3">
<h3 style="word-break: break-all">{{ roomURL }}</h3> <h3 style="word-break: break-all">{{ donStore.myStaticLink }}</h3>
</div> </div>
<div> <div>
<v-btn <v-btn
@ -221,7 +223,7 @@ export default {
>{{ $t("share") }}</v-btn >{{ $t("share") }}</v-btn
> >
<v-btn <v-btn
@click="clipboard.copy(roomURL)" @click="clipboard.copy(donStore.myStaticLink)"
color="lime" color="lime"
size="small" size="small"
:prepend-icon=" :prepend-icon="
@ -230,7 +232,10 @@ export default {
>{{ clipboard.copied.value ? $t("copied") : $t("copy") }}</v-btn >{{ clipboard.copied.value ? $t("copied") : $t("copy") }}</v-btn
> >
</div> </div>
<div class="text-center mt-10 mb-1"> <v-alert class="mt-5" density="compact" type="warning" variant="tonal">{{
$t("roomReady.timeout", { minutes: 5 })
}}</v-alert>
<div class="text-center mt-5 mb-1">
<v-btn <v-btn
color="indigo" color="indigo"
:to="{ name: 'room', params: { id: createdRoomID } }" :to="{ name: 'room', params: { id: createdRoomID } }"

Wyświetl plik

@ -10,9 +10,8 @@ export default {
if (!type) return; if (!type) return;
if (type === "offline") { if (type === "offline") {
const target = new URL(decodeURI(this.$route.query.url));
alert(this.$t("errors.offline")); alert(this.$t("errors.offline"));
window.location.href = target.toString(); this.$router.push({ name: "home" });
} }
}, },
}; };

Wyświetl plik

@ -6,6 +6,8 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"time"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
@ -30,11 +32,12 @@ type (
} }
LivekitConfig struct { LivekitConfig struct {
APIKey string `validate:"required,ascii"` APIKey string `validate:"required,ascii"`
APISecret string `validate:"required,ascii"` APISecret string `validate:"required,ascii"`
Host string `validate:"required,hostname|hostname_port"` Host string `validate:"required,hostname|hostname_port"`
LocalDomain string `validate:"required,hostname|hostname_port"` LocalDomain string `validate:"required,hostname|hostname_port"`
URL *url.URL URL *url.URL
EmptyRoomTimeout time.Duration `validate:"required"`
} }
DBConfig struct { DBConfig struct {
@ -164,11 +167,16 @@ func loadConfig(envname string) (*AppConfig, error) {
appConf.Redis = redisConf appConf.Redis = redisConf
// Setup LiveKit config // Setup LiveKit config
timeout, err := strconv.Atoi(os.Getenv("LIVEKIT_EMPTY_ROOM_TIMEOUT"))
if err != nil {
return nil, err
}
lkConf := &LivekitConfig{ lkConf := &LivekitConfig{
APIKey: os.Getenv("LIVEKIT_API_KEY"), APIKey: os.Getenv("LIVEKIT_API_KEY"),
APISecret: os.Getenv("LIVEKIT_API_SECRET"), APISecret: os.Getenv("LIVEKIT_API_SECRET"),
Host: os.Getenv("LIVEKIT_HOST"), Host: os.Getenv("LIVEKIT_HOST"),
LocalDomain: os.Getenv("LIVEKIT_LOCAL_DOMAIN"), LocalDomain: os.Getenv("LIVEKIT_LOCAL_DOMAIN"),
EmptyRoomTimeout: time.Duration(timeout) * time.Second,
} }
if err := mainValidator.Struct(lkConf); err != nil { if err := mainValidator.Struct(lkConf); err != nil {
return nil, err return nil, err

94
room.go
Wyświetl plik

@ -99,6 +99,8 @@ func createRoomHandler(c echo.Context) error {
} }
room.RoomID = canonic() room.RoomID = canonic()
room.CreatedAt = now
// if cohosts are already registered, retrieve their data from DB // if cohosts are already registered, retrieve their data from DB
for i, cohost := range room.CoHosts { for i, cohost := range room.CoHosts {
cohostUser, err := findUserByRemote(c.Request().Context(), cohost.RemoteID, cohost.RemoteURL) cohostUser, err := findUserByRemote(c.Request().Context(), cohost.RemoteID, cohost.RemoteURL)
@ -112,6 +114,37 @@ func createRoomHandler(c echo.Context) error {
return echo.NewHTTPError(http.StatusInternalServerError) return echo.NewHTTPError(http.StatusInternalServerError)
} }
// Create livekit room
roomMetadata := &RoomMetadata{Room: room, MastodonAccounts: make(map[string]*MastodonAccount)}
metadata, _ := json.Marshal(roomMetadata)
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{
Name: room.RoomID,
Metadata: string(metadata),
})
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusConflict)
}
countdown := time.NewTimer(mainConfig.Livekit.EmptyRoomTimeout)
orphanRooms.Set(room.RoomID, true, ttlcache.DefaultTTL)
go func(r *Room, logger echo.Logger) {
<-countdown.C
if orphaned := orphanRooms.Get(r.RoomID); orphaned == nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if !r.IsAnyomeInLivekitRoom(ctx) {
if err := endRoom(ctx, r); err != nil {
logger.Error(err)
}
}
}(room, c.Logger())
return c.String(http.StatusCreated, room.RoomID) return c.String(http.StatusCreated, room.RoomID)
} }
@ -402,50 +435,31 @@ func joinRoomHandler(c echo.Context) (err error) {
c.Logger().Error(err) c.Logger().Error(err)
} }
// Create room in LiveKit if it doesn't exist // Update room metadata
if lkRoom == nil { currentMeta, err := getRoomMetadataFromLivekitRoom(lkRoom)
room.CreatedAt = now if err != nil {
coll := mainDB.Collection(COLLECTION_ROOM) c.Logger().Error(err)
if _, err := coll.UpdateOne(c.Request().Context(), return echo.NewHTTPError(http.StatusInternalServerError)
bson.D{{Key: "room_id", Value: roomID}}, }
bson.D{{Key: "$set", Value: bson.D{{Key: "created_at", Value: now}}}}); err != nil { currentMeta.MastodonAccounts[user.AudonID] = mastoAccount
c.Logger().Error(err) newMetadata, err := json.Marshal(currentMeta)
return echo.NewHTTPError(http.StatusInternalServerError) if err != nil {
} c.Logger().Error(err)
metadata, _ := json.Marshal(roomMetadata) return echo.NewHTTPError(http.StatusInternalServerError)
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{ }
Name: room.RoomID, _, err = lkRoomServiceClient.UpdateRoomMetadata(c.Request().Context(), &livekit.UpdateRoomMetadataRequest{
Metadata: string(metadata), Room: roomID,
}) Metadata: string(newMetadata),
if err != nil { })
c.Logger().Error(err) if err != nil {
return echo.NewHTTPError(http.StatusConflict) c.Logger().Error(err)
} return echo.NewHTTPError(http.StatusInternalServerError)
} else {
currentMeta, err := getRoomMetadataFromLivekitRoom(lkRoom)
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
currentMeta.MastodonAccounts[user.AudonID] = mastoAccount
newMetadata, err := json.Marshal(currentMeta)
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
_, err = lkRoomServiceClient.UpdateRoomMetadata(c.Request().Context(), &livekit.UpdateRoomMetadataRequest{
Room: roomID,
Metadata: string(newMetadata),
})
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
} }
// Store user's session data in cache // Store user's session data in cache
data, _ := getSessionData(c) data, _ := getSessionData(c)
roomSessionCache.Set(user.AudonID, data, ttlcache.DefaultTTL) userSessionCache.Set(user.AudonID, data, ttlcache.DefaultTTL)
orphanRooms.Delete(roomID)
return c.JSON(http.StatusOK, resp) return c.JSON(http.StatusOK, resp)
} }

Wyświetl plik

@ -53,8 +53,9 @@ var (
mainConfig *AppConfig mainConfig *AppConfig
lkRoomServiceClient *lksdk.RoomServiceClient lkRoomServiceClient *lksdk.RoomServiceClient
localeBundle *i18n.Bundle localeBundle *i18n.Bundle
roomSessionCache *ttlcache.Cache[string, *SessionData] userSessionCache *ttlcache.Cache[string, *SessionData]
webhookTimerCache *ttlcache.Cache[string, *time.Timer] webhookTimerCache *ttlcache.Cache[string, *time.Timer]
orphanRooms *ttlcache.Cache[string, bool]
) )
func init() { func init() {
@ -154,10 +155,12 @@ func main() {
e.Use(session.Middleware(redisStore)) e.Use(session.Middleware(redisStore))
// Setup caches // Setup caches
roomSessionCache = ttlcache.New(ttlcache.WithTTL[string, *SessionData](24 * time.Hour)) userSessionCache = ttlcache.New(ttlcache.WithTTL[string, *SessionData](24 * time.Hour))
webhookTimerCache = ttlcache.New(ttlcache.WithTTL[string, *time.Timer](60 * time.Second)) webhookTimerCache = ttlcache.New(ttlcache.WithTTL[string, *time.Timer](60 * time.Second))
go roomSessionCache.Start() orphanRooms = ttlcache.New(ttlcache.WithTTL[string, bool](24 * time.Hour))
go userSessionCache.Start()
go webhookTimerCache.Start() go webhookTimerCache.Start()
go orphanRooms.Start()
e.POST("/app/login", loginHandler) e.POST("/app/login", loginHandler)
e.GET("/app/oauth", oauthHandler) e.GET("/app/oauth", oauthHandler)
@ -203,8 +206,9 @@ func main() {
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
e.Logger.Print("Attempting graceful shutdown") e.Logger.Print("Attempting graceful shutdown")
defer shutdownCancel() defer shutdownCancel()
roomSessionCache.DeleteAll() userSessionCache.DeleteAll()
webhookTimerCache.DeleteAll() webhookTimerCache.DeleteAll()
orphanRooms.DeleteAll()
if err := e.Shutdown(shutdownCtx); err != nil { if err := e.Shutdown(shutdownCtx); err != nil {
e.Logger.Fatalf("Failed shutting down gracefully: %s\n", err.Error()) e.Logger.Fatalf("Failed shutting down gracefully: %s\n", err.Error())
} }

19
user.go
Wyświetl plik

@ -10,6 +10,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
mastodon "github.com/mattn/go-mastodon" mastodon "github.com/mattn/go-mastodon"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
) )
type MastodonAccount struct { type MastodonAccount struct {
@ -77,19 +78,13 @@ func redirectUserHandler(c echo.Context) error {
return ErrUserNotFound return ErrUserNotFound
} }
rooms, err := user.GetCurrentLivekitRooms(c.Request().Context()) coll := mainDB.Collection(COLLECTION_ROOM)
if err != nil { opts := options.FindOne().SetSort(bson.D{{Key: "created_at", Value: -1}})
c.Logger().Error(err) var room Room
return echo.NewHTTPError(http.StatusInternalServerError)
}
for _, r := range rooms { if err := coll.FindOne(c.Request().Context(), bson.D{{Key: "host.audon_id", Value: user.AudonID}}, opts).Decode(&room); err == nil {
meta, err := getRoomMetadataFromLivekitRoom(r) if room.ExistsInLivekit(c.Request().Context()) {
if err != nil { return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", room.RoomID))
continue
}
if meta.Host.Equal(user) {
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", r.GetName()))
} }
} }

Wyświetl plik

@ -47,11 +47,10 @@ func livekitWebhookHandler(c echo.Context) error {
} }
still, err := user.InLivekit(c.Request().Context()) still, err := user.InLivekit(c.Request().Context())
if !still && err == nil { if !still && err == nil {
data := roomSessionCache.Get(audonID) data := userSessionCache.Get(audonID)
if data == nil { if data == nil {
return echo.NewHTTPError(http.StatusGone) return echo.NewHTTPError(http.StatusGone)
} }
roomSessionCache.Delete(audonID)
mastoClient := getMastodonClient(data.Value()) mastoClient := getMastodonClient(data.Value())
if mastoClient == nil { if mastoClient == nil {
c.Logger().Errorf("unable to get mastodon client: %v", data.Value().MastodonConfig) c.Logger().Errorf("unable to get mastodon client: %v", data.Value().MastodonConfig)
@ -93,6 +92,7 @@ func livekitWebhookHandler(c echo.Context) error {
log.Println(err) log.Println(err)
} }
nextUser.ClearUserAvatar(ctx) nextUser.ClearUserAvatar(ctx)
userSessionCache.Delete(audonID)
} else if err != nil { } else if err != nil {
log.Println(err) log.Println(err)
} }