Merge pull request #3635 from fschade/feat-collaboration-eurooffice-mobile-view

feat(collaboration): mobile web view for EuroOffice
This commit is contained in:
Florian Schade authored and GitHub committed 2026-10-02 10:16:08 +02:00
commit 94448630e3
5 files changed
+87

No files matched your search

+6
View File
@@ -32,6 +32,9 @@ There are a few variables that you need to set:
The product name of the connected WebOffice app, which can be one of the following:\
`Collabora`, `OnlyOffice`, `Microsoft365` or `MicrosoftOfficeOnline`. This is used to internally control the behavior according to the different features of the used products.
* `COLLABORATION_APP_PRODUCT_EDITION`:\
The edition of the connected WebOffice app, it decides which features the app offers. Only used for EuroOffice, which supports `ce` (community edition), `de` (developer edition) and `ee` (enterprise edition). An empty value is the same as `ce`.
* `COLLABORATION_APP_ADDR`:\
The URL of the collaborative editing app (onlyoffice, collabora, etc).\
For example: `https://office.example.com`.
@@ -46,6 +49,9 @@ There are a few variables that you need to set:
* `COLLABORATION_WOPI_SHORTTOKENS`:\
Needs to be set if the office application like `Microsoft Office Online` complains about the URL is too long (which contains the access token) and refuses to work. If enabled, a store must be configured.
* `COLLABORATION_WOPI_ENABLE_MOBILE`:\
Enables the mobile web view of the office web frontend. Only applies to EuroOffice. `ce` offers the mobile view for reading only, `de` and `ee` also for editing, so set `COLLABORATION_APP_PRODUCT_EDITION` accordingly.
The application can be customized further by changing the `COLLABORATION_APP_*` options to better describe the application.
## Storing
+2
View File
@@ -12,6 +12,8 @@ type App struct {
ProofKeys ProofKeys `yaml:"proofkeys"`
LicenseCheckEnable bool `yaml:"licensecheckenable" env:"COLLABORATION_APP_LICENSE_CHECK_ENABLE" desc:"Enable license checking to edit files. Needs to be enabled when using Microsoft365 with the business flow." introductionVersion:"1.0.0"`
ProductEdition string `yaml:"product_edition" env:"COLLABORATION_APP_PRODUCT_EDITION" desc:"The edition of the WebOffice app, it decides which features the app offers. Only used for EuroOffice, where 'ce', 'de' and 'ee' are supported and an empty value is the same as 'ce'." introductionVersion:"%%NEXT%%"`
}
type ProofKeys struct {
@@ -8,4 +8,6 @@ type Wopi struct {
ProxyURL string `yaml:"proxy_url" env:"COLLABORATION_WOPI_PROXY_URL" desc:"The URL to the OpenCloud WOPI proxy. Optional. To use this feature, you need an office365 proxy subscription. If you become part of the Microsoft CSP program (https://learn.microsoft.com/en-us/partner-center/enroll/csp-overview), you can use WebOffice without a proxy." introductionVersion:"1.0.0"`
ProxySecret string `yaml:"proxy_secret" env:"COLLABORATION_WOPI_PROXY_SECRET" desc:"Optional, the secret to authenticate against the OpenCloud WOPI proxy. This secret can be obtained from OpenCloud via the office365 proxy subscription." introductionVersion:"1.0.0"`
ShortTokens bool `yaml:"short_tokens" env:"COLLABORATION_WOPI_SHORTTOKENS" desc:"Use short access tokens for WOPI access. This is useful for office packages, like Microsoft Office Online, which have URL length restrictions. If enabled, a persistent store must be configured." introductionVersion:"1.0.0"`
EnableMobile bool `yaml:"enable_mobile" env:"COLLABORATION_WOPI_ENABLE_MOBILE" desc:"Enable the mobile web view of the office web frontend. This feature applies to EuroOffice, where the product edition decides whether it covers editing as well." introductionVersion:"%%NEXT%%"`
}
@@ -239,6 +239,20 @@ func (s *Service) addQueryToURL(baseURL string, req *appproviderv1beta1.OpenInAp
q.Add("dchat", "1")
}
// EuroOffice ce offers the mobile view for reading only, ee and de also for editing.
if s.config.Wopi.EnableMobile && strings.ToLower(s.config.App.Product) == "onlyoffice" {
if mobile := utils.ReadPlainFromOpaque(req.GetOpaque(), "mobile"); mobile != "" {
switch strings.ToLower(s.config.App.ProductEdition) {
case "ee", "de":
q.Add("mobile", mobile)
default:
if req.GetViewMode() != appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE {
q.Add("mobile", mobile)
}
}
}
}
lang := utils.ReadPlainFromOpaque(req.GetOpaque(), "lang")
// @TODO: this is a temporary solution until we figure out how to send these from oc web
@@ -2,6 +2,7 @@ package service_test
import (
"context"
"net/url"
"strconv"
"time"
@@ -208,6 +209,68 @@ var _ = Describe("Discovery", func() {
Entry("Collabora no chat lang", "Collabora", "de", true, "https://cloud.opencloud.test/hosting/wopi/word/view?WOPISrc=https%3A%2F%2Fwopi.opencloud.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&closebutton=false&dchat=1&lang=de-DE"),
Entry("OnlyOffice no chat lang", "OnlyOffice", "de", true, "https://cloud.opencloud.test/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fwopi.opencloud.test%2Fwopi%2Ffiles%2F2f6ec18696dd1008106749bd94106e5cfad5c09e15de7b77088d03843e71b43e&dchat=1&ui=de-DE"),
)
DescribeTable(
"Mobile view",
func(product, edition string, enableMobile bool, viewMode appproviderv1beta1.ViewMode, mobile, expectedMobile string) {
ctx := context.Background()
nowTime := time.Now()
cfg.Wopi.WopiSrc = "https://wopi.opencloud.test"
cfg.Wopi.Secret = "my_supa_secret"
cfg.Wopi.EnableMobile = enableMobile
cfg.App.Name = product
cfg.App.Product = product
cfg.App.ProductEdition = edition
myself := &userv1beta1.User{
Id: &userv1beta1.UserId{
Idp: "myIdp",
OpaqueId: "opaque001",
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
},
Username: "username",
}
req := &appproviderv1beta1.OpenInAppRequest{
ResourceInfo: &providerv1beta1.ResourceInfo{
Id: &providerv1beta1.ResourceId{
StorageId: "myStorage",
OpaqueId: "storageOpaque001",
SpaceId: "SpaceA",
},
Path: "/path/to/file.docx",
},
ViewMode: viewMode,
AccessToken: MintToken(myself, cfg.Wopi.Secret, nowTime),
}
if mobile != "" {
req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "mobile", mobile)
}
gatewayClient.On("WhoAmI", mock.Anything, mock.Anything).Times(1).Return(&gatewayv1beta1.WhoAmIResponse{
Status: status.NewOK(ctx),
User: myself,
}, nil)
resp, err := srv.OpenInApp(ctx, req)
Expect(err).To(Succeed())
Expect(resp.GetStatus().GetCode()).To(Equal(rpcv1beta1.Code_CODE_OK))
appURL, err := url.Parse(resp.GetAppUrl().GetAppUrl())
Expect(err).To(Succeed())
Expect(appURL.Query().Get("mobile")).To(Equal(expectedMobile))
},
Entry("ee edits on mobile", "OnlyOffice", "ee", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "true", "true"),
Entry("de edits on mobile", "OnlyOffice", "de", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "true", "true"),
Entry("edition is matched case-insensitively", "OnlyOffice", "EE", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "true", "true"),
Entry("ce does not edit on mobile", "OnlyOffice", "ce", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "true", ""),
Entry("an empty edition is ce", "OnlyOffice", "", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "true", ""),
Entry("ce reads on mobile", "OnlyOffice", "ce", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_ONLY, "true", "true"),
Entry("disabled stays out of the url", "OnlyOffice", "ee", false, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "true", ""),
Entry("without a mobile request nothing is added", "OnlyOffice", "ee", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, "", ""),
Entry("collabora does not get it", "Collabora", "ee", true, appproviderv1beta1.ViewMode_VIEW_MODE_READ_ONLY, "true", ""),
)
It("Success with Wopi Proxy", func() {
ctx := context.Background()
nowTime := time.Now()