angular SwPush.subscription & SwPush.requestSubscription 在硬重载后没有发出,

qncylg1j  于 6个月前  发布在  Angular
关注(0)|答案(3)|浏览(53)

哪个@angular/*包是bug的来源?

service-worker

这是回归吗?

问题描述

我在Angular 13中发现了这个问题,但它也出现在v14和v15上。
我们使用SwPush实现了推送通知。
在组件初始化时,我想订阅SwPush.subscription(Observable),以获取当前订阅(如果没有,则为null)。
同样,当我最终想请求用户权限时,我想调用SwPush.requestSubscription(Promise)。
当我强制重新加载应用程序(按STRG+F5)时,这两个方法都不会发出任何值(或解析Promise)。它们不会抛出任何错误,只是保持沉默,永远不会完成。
当我软重新加载应用程序(正常刷新页面)时,只有这时这些方法才会起作用并发出相应的值。

预期行为:

在订阅.subscription Observable或使用requestSubscription() Promise时发出值,或者如果出现问题/失败则抛出错误。

当前行为:

什么都没有发出,没有错误抛出,用户将永远等待。

请提供一个链接,以最小化地重现bug

  • 无响应*

请提供您看到的异常或错误

  • 无响应*

请提供您发现此bug的环境(运行ng version)

Angular CLI: 15.1.0
Node: 16.14.2
Package Manager: npm 8.5.5
OS: win32 x64

Angular: 15.1.0
... animations, cli, common, compiler, compiler-cli, core
... elements, forms, language-service, localize
... platform-browser, platform-browser-dynamic, router
... service-worker

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1501.0
@angular-devkit/build-angular   15.1.0
@angular-devkit/core            15.1.0
@angular-devkit/schematics      15.1.0
@angular/cdk                    14.2.7
@schematics/angular             9.1.15
rxjs                            6.6.7
typescript                      4.8.4
webpack                         4.46.0

还有其他信息吗?

  • 无响应*
np8igboo

np8igboo1#

我也遇到了这个问题。我在这里制作了一个复制品 https://github.com/jamsjordn/angular-push-request-hard-reload-issue
ng newng add @angular/pwa 制作的样板

eufgjt7s

eufgjt7s2#

我们也在Angular v16上遇到了这个问题。进行了一些深入的调试,发现问题出在这段代码中:

const currentController = defer(() => of(serviceWorker.controller));
      const controllerWithChanges = concat(currentController, controllerChanges);

      this.worker = controllerWithChanges.pipe(filter((c): c is ServiceWorker => !!c));

      this.registration = <Observable<ServiceWorkerRegistration>>(
          this.worker.pipe(switchMap(() => serviceWorker.getRegistration())));

这里的问题是,当你从硬刷新进入应用程序时,currentController 会发出 null 。这与描述的规范一致。
然而,控制器为null并不一定意味着没有注册,没有注册的发出意味着没有发出pushManager,这导致SwPush.subscription和SwPush.requestSubscription不发出或解析任何内容,即使存在现有的订阅。
我建议的修复方法如下:

const currentController = defer(() => of(serviceWorker.controller));
      const controllerWithChanges = concat(currentController, controllerChanges);

      this.worker = controllerWithChanges.pipe(filter((c): c is ServiceWorker => !!c));

      this.registration = <Observable<ServiceWorkerRegistration>>(
          concat(
              currentController, 
              controllerChanges.pipe(
                  filter((c): c is ServiceWorker => !!c)
              )
          ).pipe(
              switchMap(() => serviceWorker.getRegistration()),
              filter((swr): swr is ServiceWorkerRegistration => !!swr)
          )
      );

这实际上使得即使当前控制器为null,也会尝试获取ServiceWorkerRegistration。虽然这个方法有效,但我唯一怀疑的是,我对Angular的服务工作实现了解不足,无法判断这是否会产生任何意外的副作用。如果有更了解的开发人员能提供帮助,将不胜感激!:)
在此期间,我制作了一个hacky方法,可以在注入swPush服务的组件/服务中使用此修复:

private fixSwPush(): void {
    if (!this.swPush.isEnabled) {
      return;
    }

    const serviceWorker: ServiceWorkerContainer | undefined = this.swPush?.['sw']?.['serviceWorker'];

    if (!serviceWorker) {
      return;
    }

    // Adapted from original code:
    // https://github.com/angular/angular/blob/c3b00959659ca20d3f798820dfcb4dee250a32ac/packages/service-worker/src/low_level.ts#L137
    const controllerChangeEvents = fromEvent(serviceWorker, 'controllerchange');
    const controllerChanges = controllerChangeEvents.pipe(map(() => serviceWorker.controller));
    const currentController = defer(() => of(serviceWorker.controller));

    const registration = concat(
      // The actual fix, here we don't do the falsy filtering for the currentController (as it is null on a hard refresh)
      currentController,
      controllerChanges.pipe(
        filter((c): c is ServiceWorker => !!c),
      )
    ).pipe(
      // Instead just try to get the ServiceWorkerRegistration here
      switchMap(() => serviceWorker.getRegistration()),
      // And filter out any falsy ServiceWorkerRegistration at the end
      filter((swr): swr is ServiceWorkerRegistration => !!swr)
    );

    // Adapted from original code:
    // https://github.com/angular/angular/blob/c3b00959659ca20d3f798820dfcb4dee250a32ac/packages/service-worker/src/push.ts#L151
    const pushManager = registration.pipe(map(registration => registration.pushManager));

    // Overwrite the original pushManager
    this.swPush['pushManager'] = pushManager;

    const workerDrivenSubscriptions = pushManager.pipe(switchMap(pm => pm.getSubscription()));

    // Overwrite the original subscriptionChanges
    (this.swPush.subscription as Observable<PushSubscription | null>) = merge(workerDrivenSubscriptions, (this.swPush['subscriptionChanges'] as Subject<PushSubscription | null>));

  }

并在其他任何事情之前(甚至字段初始化器)执行此操作:

@Injectable({
  providedIn: 'root'
})
export class WebPushService implements OnDestroy {

  // TODO: There is a problem with this not emitting after a hard reload https://github.com/angular/angular/issues/48702
  // this is a hacky way to fix this. At some point reevaluate this solution
  private readonly runThisFirst = this.fixSwPush();
  
  constructor(
    private readonly swPush: SwPush,
...

我希望这可以帮助那些陷入困境的人,也希望这个问题能在某个时候得到正确的解决(如果有什么我可以做的来帮助解决这个问题,请告诉我?)

3duebb1j

3duebb1j3#

请查看以下链接:

  1. https://developer.chrome.com/docs/workbox/improving-development-experience#shift_and_reload
  2. https://web.dev/articles/service-worker-lifecycle#shift-reload
    When a service worker is active, a forced refresh will also bypass the service worker entirely.

相关问题