Flutter - platformViewRegistry для iFrame, создающий несколько представлений, и setState(), блокирующий выполнение <script> в Iframe

Для обработки платежей в нашем веб-приложении Flutter мы используем форму, размещенную в элементе IFrame, чтобы браузер клиента мог напрямую подключаться к платежному шлюзу. При отправке формы шлюз возвращает токен, который мы используем для настройки учетной записи в нашей серверной части без необходимости прямого доступа или хранения какой-либо информации о карте. Код JavaScript для формы, включая кнопку «Отправить», находится в srcdoc для IFrameElement.

Когда клиент получает токен, мы используем window.parent.postMessage (с надлежащими проверками безопасности), чтобы передать его из IFrame в код Flutter, где его прослушивает подписка на поток. Когда прослушиватель получает токен, он переводит клиента на следующий этап процесса регистрации.

Я пытаюсь сделать так, чтобы щелчок «отправить» запускал событие, которое изменяет состояние виджета для отображения круга загрузки, а затем триггер получения токена переключает состояние обратно в нормальное состояние и продолжает процесс регистрации. Однако, когда я обнаруживаю «отправить» и вызываю setState(), кажется, что остальная часть процесса блокируется, и я получаю вечный круг загрузки.

ОБНОВЛЯТЬ

Мне удалось создать желаемый эффект в Javascript iFrame, но я все равно хотел бы знать:

  1. Почему описанный вызов Flutter для setState() не работает должным образом,
  2. Почему этот код, кажется, создает несколько представлений (о чем свидетельствует журнал «viewNum = 2»; это число продолжает увеличиваться при тестировании, если я не обновляю браузер с помощью Shift) и
  3. Как отменить/удалить множественное представление и избежать их создания.
      import 'dart:async';
import "package:flutter/material.dart";
import 'package:project/theme/branding.dart';
import 'package:project/widgets/account/version_select_form.dart';
import "dart:ui" as ui;
import "package:universal_html/html.dart";

class CreditCardForm extends StatefulWidget {
  CreditCardForm(this._formKey,)

  final _formKey;

  @override
  State<CreditCardForm> createState() => _CreditCardFormState();
}

class _CreditCardFormState extends State<CreditCardForm> {
  _CreditCardFormState() {
    _registerIFrame();
  }

  late String formCode = getFormCode();
  String? token;
  late StreamSubscription tokenSub;
  late StreamSubscription submitSub;
  bool _isProcessing = false;

  _registerIFrame() {
    IFrameElement _billingInfo = IFrameElement();
    _billingInfo.srcdoc = formCode;

    // ignore: undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(
      "_billingInfo",
      (int viewId) => _billingInfo,
    );

    var submitListener = (event) {
      if (event.type == "submit") {
        debugPrint("heard ${event.type} in credit card form"); //logs: heard submit in credit card form
        submitSub.cancel();
        // setState(() {  //this setState() seems to block the token process from proceeding
        //   _isProcessing = true;
        // });
      } else {
        debugPrint("ignored ${event.type}");
      }
    };

    var tokenListener = (event) async {
      tokenSub.cancel();
      // setState(() {
      //   _isProcessing = false;
      // });
      debugPrint("event.type for token message = ${event.type}"); //logs: event.type for token message = message
      if (event.origin != window.location.origin) 
        debugPrint('nice try!');
      } else {
        token = event.data;
        debugPrint(token); //logs token
        tokenSub.cancel();
        Navigator.pop(context);
        showDialog(
          context: context,
          builder: (context) =>
              Dialog(child: VersionSelectForm(token: token!)),
        );
      }
    }
  };

    tokenSub = window.onMessage.listen(tokenListener);
    submitSub = window.onSubmit.listen(submitListener);

  Widget build(BuildContext context) {
    return Column(
      children: [
        Form(
          key: widget._formKey,
          child: SizedBox(
            width: 550,
            height: 250,
            child: HtmlElementView(
              key: UniqueKey(),
              viewType: "_billingInfo",
              onPlatformViewCreated: (int viewNum) {
                debugPrint('viewNum: $viewNum'); //logs: viewNum: 2 (I don't know why it's not 0)
              },
            ),
          ),
        ),
        _isProcessing
            ? Center(
                child: CircularProgressIndicator(
                  color: BrandColors.orange,
                ),
              )
            : Container()
      ],
    );
  }
}

getFormCode() {
  return """
<!DOCTYPE html>
<html>
    <head>
        <script src="https://<script src>"></script>
    </head>
    <style>
        // styling info
    </style>

    <body>
        <form class="host-form" id="billing-form">
            //a bunch of html form elements

            <div class="form-field submit-row">
              <button id="submit-button" type="submit">CONTINUE</button>
            </div>
        </form>
    </body>

    <script>
        var billingSite = new BillingSite();
        billingSite.load({
            selector: "billing-form",
            publicKey: "**********",
            type: "card",
            serverHost: "https://our-store.billing-site.com",
        });
        
    //Without this vvv the submit listener above in _registerIFrame() does not respond,
    //even though the next event listener is also for 'submit'

     document.querySelector("#billing-form").addEventListener("submit", function(event) {
       event.preventDefault();
         console.log("event.type: "+event.type) //logs: event.type: submit
         window.parent.dispatchEvent(new Event('submit'))
       }
     )

    document.querySelector("#billing-form").addEventListener("submit", function(event) {
    var form = this;

    event.preventDefault();
    billingSite.token(
        form,
        function success(token) {
            window.parent.postMessage(token, window.parent.location.origin);
        },
        function error(err) {
            console.log("token ERROR - err: ", err);
        }
    );

    
});


    </script>
</html>

""";
}

1 ответ

Оказывается, если дать представлению другое имя, оно сможет отображать разные заголовки. То есть отправьте viewName в качестве параметра и используйте его для регистрации viewFactory и отображения viewType:

      ui.platformViewRegistry.registerViewFactory(
      viewName, //"newPaymentInfo" or "updatePaymentInfo" for example
      ...

Убедитесь, что HtmlElementView отражает имя в свойстве viewType:

          return SizedBox(
        height: widget.screenHeight,
        width: screenWidth,
        child: HtmlElementView(
          key: ValueKey("paymentFormKey"),
          viewType: viewName, //again, "newPaymentInfo" or "updatePaymentInfo" for example
          onPlatformViewCreated: (int viewNum) {
            debugPrint('viewNum: $viewNum'); //This usually logs '2' the first time and increments from there
          },
        ));
  }

Никаких других частей кода менять не пришлось (кроме принятия viewName в качестве параметра).

Другие вопросы по тегам